diff --git a/.asf.yaml b/.asf.yaml new file mode 100644 index 00000000000..b1872d166bd --- /dev/null +++ b/.asf.yaml @@ -0,0 +1,53 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 00000000000..7333057fba1 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,79 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 +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 + +# Set several flags related to specifying the platform, toolchain and java +# properties. +# These flags should only be used as is for the rbe-ubuntu16-04 container +# and need to be adapted to work with other toolchain containers. +build:remote --java_runtime_version=rbe_jdk +build:remote --tool_java_runtime_version=rbe_jdk +build:remote --extra_toolchains=@rbe_default//java:all + +build:remote --crosstool_top=@rbe_default//cc:toolchain +build:remote --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 +# Platform flags: +# The toolchain container used for execution is defined in the target indicated +# by "extra_execution_platforms", "host_platform" and "platforms". +# More about platforms: https://docs.bazel.build/versions/master/platforms.html +build:remote --extra_toolchains=@rbe_default//config:cc-toolchain +build:remote --extra_execution_platforms=@rbe_default//config:platform +build:remote --host_platform=@rbe_default//config:platform +build:remote --platforms=@rbe_default//config:platform + +# Starting with Bazel 0.27.0 strategies do not need to be explicitly +# defined. See https://github.com/bazelbuild/bazel/issues/7480 +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 \ No newline at end of file diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 00000000000..7cbea073bea --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +5.2.0 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000000..d46c3cd2ac7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,113 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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. +labels: [ "type/bug" ] +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 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..26e9315355c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +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 diff --git a/.github/ISSUE_TEMPLATE/enhancement_request.yml b/.github/ISSUE_TEMPLATE/enhancement_request.yml new file mode 100644 index 00000000000..0d15db8642f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement_request.yml @@ -0,0 +1,57 @@ +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 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000000..c894e6d44c9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,40 @@ +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 diff --git a/.github/ISSUE_TEMPLATE/issue_template.md b/.github/ISSUE_TEMPLATE/issue_template.md deleted file mode 100644 index 1c8fa94aac2..00000000000 --- a/.github/ISSUE_TEMPLATE/issue_template.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: ISSUE_TEMPLATE -about: Describe this issue template's purpose here. - ---- - -The issue tracker is **ONLY** used for bug report(feature request need to follow [RIP process](https://github.com/apache/rocketmq/wiki/RocketMQ-Improvement-Proposal)). Keep in mind, please check whether there is an existing same report before your raise a new one. - -Alternately (especially if your communication is not a bug report), you can send mail to our [mailing lists](http://rocketmq.apache.org/about/contact/). We welcome any friendly suggestions, bug fixes, collaboration and other improvements. - -Please ensure that your bug report is clear and that it is complete. Otherwise, we may be unable to understand it or to reproduce it, either of which would prevent us from fixing the bug. We strongly recommend the report(bug report or feature request) could include some hints as the following: - -**BUG REPORT** - -1. Please describe the issue you observed: - -- What did you do (The steps to reproduce)? - -- What did you expect to see? - -- What did you see instead? - -2. Please tell us about your environment: - -3. Other information (e.g. detailed explanation, logs, related issues, suggestions how to fix, etc): - -**FEATURE REQUEST** - -1. Please describe the feature you are requesting. - -2. Provide any additional detail on your proposed use case for this feature. - -2. Indicate the importance of this issue to you (blocker, must-have, should-have, nice-to-have). Are you currently using any workarounds to address this issue? - -4. If there are some sub-tasks using -[] for each subtask and create a corresponding issue to map to the sub task: - -- [sub-task1-issue-number](example_sub_issue1_link_here): sub-task1 description here, -- [sub-task2-issue-number](example_sub_issue2_link_here): sub-task2 description here, -- ... diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1eb8c834382..96bffa55a3f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,23 +1,15 @@ + -**Make sure set the target branch to `develop`** +### Which Issue(s) This PR Fixes -## What is the purpose of the change + -XXXXX +Fixes #issue_id -## Brief changelog +### Brief Description -XX + -## Verifying this change +### How Did You Test This Change? -XXXX - -Follow this checklist to help us incorporate your contribution quickly and easily. Notice, `it would be helpful if you could finish the following 5 checklist(the last one is not necessary)before request the community to review your PR`. - -- [x] Make sure there is a [Github issue](https://github.com/apache/rocketmq/issues) filed for the change (usually before you start working on it). Trivial changes like typos do not require a Github issue. Your pull request should address just this issue, without pulling in other changes - one PR resolves one issue. -- [x] Format the pull request title like `[ISSUE #123] Fix UnknownException when host config not exist`. Each commit in the pull request should have a meaningful subject line and body. -- [x] Write a pull request description that is detailed enough to understand what the pull request does, how, and why. -- [x] Write necessary unit-test(over 80% coverage) to verify your logic correction, more mock a little better when cross module dependency exist. If the new feature or significant change is committed, please remember to add integration-test in [test module](https://github.com/apache/rocketmq/tree/master/test). -- [x] Run `mvn -B clean apache-rat:check findbugs:findbugs checkstyle:checkstyle` to make sure basic checks pass. Run `mvn clean install -DskipITs` to make sure unit-test pass. Run `mvn clean test-compile failsafe:integration-test` to make sure integration-test pass. -- [ ] If this contribution is large, please file an [Apache Individual Contributor License Agreement](http://www.apache.org/licenses/#clas). + diff --git a/.github/asf-deploy-settings.xml b/.github/asf-deploy-settings.xml new file mode 100644 index 00000000000..fad16cfb808 --- /dev/null +++ b/.github/asf-deploy-settings.xml @@ -0,0 +1,33 @@ + + + + + + + + apache.snapshots.https + ${env.NEXUS_DEPLOY_USERNAME} + ${env.NEXUS_DEPLOY_PASSWORD} + + + + \ No newline at end of file diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml new file mode 100644 index 00000000000..7652b93048c --- /dev/null +++ b/.github/workflows/bazel.yml @@ -0,0 +1,22 @@ +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 --nocache_test_results //... \ No newline at end of file diff --git a/.github/workflows/codeql_analysis.yml b/.github/workflows/codeql_analysis.yml new file mode 100644 index 00000000000..e5e8d323a29 --- /dev/null +++ b/.github/workflows/codeql_analysis.yml @@ -0,0 +1,32 @@ +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 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000000..81db2a656cb --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,24 @@ +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@v2 + with: + java-version: "8" + distribution: "adopt" + 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 diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml deleted file mode 100644 index b95e6e4bd03..00000000000 --- a/.github/workflows/greetings.yml +++ /dev/null @@ -1,29 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT 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: Little RocketMQ -on: [pull_request_target, issues] -uses: actions/first-interaction@v1.1.0 -with: - # Token for the repository. Can be passed in using {{ secrets.GITHUB_TOKEN }} - repo-token: ${{ secrets.GITHUB_TOKEN }} - # Comment to post on an individual's first issue - issue-message: 'Make sure your issue is not the existence through the issue search. Follow the issue template, make more details for us. But please be aware that Issue should not be used for FAQs: if you have a question or are simply not sure if it is really an issue or not, please contact [us](https://rocketmq.apache.org/about/contact/) first before you create a new issue.' - # Comment to post on an individual's first pull request - pr-message: '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/).' diff --git a/.github/workflows/license-checker.yaml b/.github/workflows/license-checker.yaml new file mode 100644 index 00000000000..259fdd7ffa7 --- /dev/null +++ b/.github/workflows/license-checker.yaml @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml new file mode 100644 index 00000000000..75bf91eb18f --- /dev/null +++ b/.github/workflows/maven.yaml @@ -0,0 +1,27 @@ +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@v2 + with: + java-version: ${{ matrix.jdk }} + distribution: "adopt" + cache: "maven" + - name: Build with Maven + run: mvn -B package --file pom.xml diff --git a/.github/workflows/misspell_check.yml b/.github/workflows/misspell_check.yml new file mode 100644 index 00000000000..81729e42a41 --- /dev/null +++ b/.github/workflows/misspell_check.yml @@ -0,0 +1,17 @@ +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 diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml new file mode 100644 index 00000000000..ef2db755d00 --- /dev/null +++ b/.github/workflows/pr-ci.yml @@ -0,0 +1,36 @@ +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@v3 + 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@v2 + with: + name: pr + path: pr/ diff --git a/.github/workflows/pr-e2e-test.yml b/.github/workflows/pr-e2e-test.yml new file mode 100644 index 00000000000..d0371e31135 --- /dev/null +++ b/.github/workflows/pr-e2e-test.yml @@ -0,0 +1,257 @@ +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: + runs-on: ubuntu-latest + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' + timeout-minutes: 30 + strategy: + matrix: + base-image: ["ubuntu"] + java-version: ["8"] + steps: + - name: 'Download artifact' + uses: actions/github-script@v3.1.0 + with: + script: | + var artifacts = await github.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{github.event.workflow_run.id }}, + }); + var matchArtifactRmq = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "rocketmq" + })[0]; + var download = await github.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@v3 + name: Upload distribution tar + with: + name: versionlist + path: rocketmq-docker/image-build-ci/versionlist/* + + list-version: + if: 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@v3 + 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@v3 + 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 + 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@v3 + 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@v3 + 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 }} + diff --git a/.github/workflows/push-ci.yml b/.github/workflows/push-ci.yml new file mode 100644 index 00000000000..ad29a57c8a8 --- /dev/null +++ b/.github/workflows/push-ci.yml @@ -0,0 +1,262 @@ +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: + 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@v3 + 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@v3 + 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@v3 + name: Upload distribution tar + with: + name: versionlist + path: rocketmq-docker/image-build-ci/versionlist/* + + + list-version: + if: 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@v3 + 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@v3 + 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 + 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@v3 + 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@v3 + 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 }} + diff --git a/.github/workflows/snapshot-automation.yml b/.github/workflows/snapshot-automation.yml new file mode 100644 index 00000000000..4691e602699 --- /dev/null +++ b/.github/workflows/snapshot-automation.yml @@ -0,0 +1,26 @@ +name: Snapshot Release Automation +on: + schedule: # schedule the job to run at 12 a.m. daily + - cron: "0 0 * * *" + +jobs: + snapshot: + runs-on: ubuntu-latest + 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@v2 + with: + java-version: 8 + distribution: "adopt" + cache: "maven" + - name: Deploy to ASF Snapshots Repository + timeout-minutes: 40 + run: mvn clean deploy -DskipTests=true --settings .github/asf-deploy-settings.xml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000000..ca1a153e76f --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,32 @@ +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 diff --git a/.gitignore b/.gitignore index 264f48d0dd7..c20f4bf7685 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ .settings/ target/ devenv -*.log* +*.log.* *.iml .idea/ *.versionsBackup @@ -13,3 +13,8 @@ devenv .DS_Store localbin nohup.out +bazel-out +bazel-bin +bazel-rocketmq +bazel-testlogs +.vscode \ No newline at end of file diff --git a/.licenserc.yaml b/.licenserc.yaml new file mode 100644 index 00000000000..f74fb8c5dec --- /dev/null +++ b/.licenserc.yaml @@ -0,0 +1,50 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT 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 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5f08c8500b8..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,58 +0,0 @@ -dist: bionic - -notifications: - email: - recipients: - - dev@rocketmq.apache.org - if: branch = develop OR branch = master - on_success: change - on_failure: always - - -language: java - -matrix: - include: - # On OSX, run with default JDK only. - # - os: osx - # On Linux we install latest OpenJDK 1.8 from Ubuntu repositories - - name: Linux x86_64 - arch: amd64 - - name: Linux aarch64 - dist: focal - arch: arm64-graviton2 - group: edge - virt: vm - -cache: - directories: - - $HOME/.m2/repository - -before_install: - - lscpu - - echo 'MAVEN_OPTS="$MAVEN_OPTS -Xmx1024m -XX:MaxPermSize=512m -XX:+BytecodeVerificationLocal"' >> ~/.mavenrc - - cat ~/.mavenrc - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export JAVA_HOME=$(/usr/libexec/java_home); fi - -install: | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - sudo apt update - sudo apt install -y openjdk-8-jdk maven - export JAVA_HOME="/usr/lib/jvm/java-8-openjdk-${TRAVIS_CPU_ARCH}/" - export PATH="$JAVA_HOME/bin:/usr/share/maven/bin:$PATH" - fi - -before_script: - - java -version - - mvn -version - - ulimit -c unlimited - -script: - - mvn -B verify -DskipTests - - travis_retry mvn -B clean apache-rat:check - - travis_retry mvn -B install jacoco:report coveralls:report - - travis_retry mvn -B clean install -pl test -Pit-test - -after_success: - - mvn sonar:sonar -Psonar-apache - - bash <(curl -s https://codecov.io/bash) || echo 'Codecov failed to upload' diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 00000000000..358527c3149 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,48 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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_toolchains//rules/exec_properties:exec_properties.bzl", "create_rbe_exec_properties_dict") + +platform( + name = "custom_platform", + # Inherit from the platform target generated by 'rbe_configs_gen' assuming the generated configs + # were imported as a Bazel external repository named 'rbe_default'. If you extracted the + # generated configs elsewhere in your source repository, replace the following with the label + # to the 'platform' target in the generated configs. + parents = ["@rbe_default//config:platform"], + # Example custom execution property instructing RBE to use e2-standard-2 GCE VMs. + exec_properties = create_rbe_exec_properties_dict( + container_image = "ubuntu:latest", + ), +) + +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", + ], +) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 952ad7398a9..f3386e8c9d0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,10 @@ We want to have high quality, well documented codes for each programming languag Nor is code the only way to contribute to the project. We strongly value documentation, integration with other project, 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: @@ -13,6 +17,19 @@ To submit a change for inclusion, please do the following: #### 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 your 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. @@ -25,7 +42,7 @@ Nowadays,we have several important contribution points: ##### Prerequisite If you want to contribute the above listing points, you must abide our some prerequisites: -###### Readability - API must have Javadoc,some very important methods also must have javadoc +###### Readability - API must have Javadoc, some very important methods also must have javadoc ###### Testability - 80% above unit test coverage about main process ###### Maintainability - Comply with our [checkstyle spec](style/rmq_checkstyle.xml), and at least 3 month update frequency ###### Deployability - We encourage you to deploy into [maven repository](http://search.maven.org/) diff --git a/NOTICE b/NOTICE index a347efb1756..2a636577710 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Apache RocketMQ -Copyright 2016-2022 The Apache Software Foundation +Copyright 2016-2023 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). diff --git a/README.md b/README.md index 41392af2eae..f0bb22c4a91 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,29 @@ -## Apache RocketMQ -[![Build Status](https://travis-ci.org/apache/rocketmq.svg?branch=master)](https://travis-ci.org/apache/rocketmq) [![Coverage Status](https://coveralls.io/repos/github/apache/rocketmq/badge.svg?branch=master)](https://coveralls.io/github/apache/rocketmq?branch=master) -[![CodeCov](https://codecov.io/gh/apache/rocketmq/branch/master/graph/badge.svg)](https://codecov.io/gh/apache/rocketmq) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.rocketmq/rocketmq-all/badge.svg)](http://search.maven.org/#search%7Cga%7C1%7Corg.apache.rocketmq) -[![GitHub release](https://img.shields.io/badge/release-download-orange.svg)](https://rocketmq.apache.org/dowloading/releases) -[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) -[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/apache/rocketmq.svg)](http://isitmaintained.com/project/apache/rocketmq "Average time to resolve an issue") -[![Percentage of issues still open](http://isitmaintained.com/badge/open/apache/rocketmq.svg)](http://isitmaintained.com/project/apache/rocketmq "Percentage of issues still open") -[![Twitter Follow](https://img.shields.io/twitter/follow/ApacheRocketMQ?style=social)](https://twitter.com/intent/follow?screen_name=ApacheRocketMQ) +## 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][percentage-of-issues-still-open-image]][pencentage-of-issues-still-open-url] +[![Percentage of Issues Still Open][average-time-to-resolve-an-issue-image]][average-time-to-resolve-an-issue-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 base on [DLedger](https://github.com/openmessaging/openmessaging-storage-dledger) -* A variety of cross language clients, such as Java, [C/C++](https://github.com/apache/rocketmq-client-cpp), [Python](https://github.com/apache/rocketmq-client-python), [Go](https://github.com/apache/rocketmq-client-go), [Node.js](https://github.com/apache/rocketmq-client-nodejs) -* Pluggable transport protocols, such as TCP, SSL, AIO +* Built-in fault tolerance and high availability configuration options base on [DLedger Controller](docs/en/controller/quick_start.md) * Built-in message tracing capability, also support 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 JMS and OpenMessaging +* 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 @@ -34,18 +34,159 @@ It offers a variety of features: * 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.1.1/rocketmq-all-5.1.1-bin-release.zip) to download the 5.1.1 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.1.1/rocketmq-all-5.1.1-bin-release.zip + +# Unpack the release +$ unzip rocketmq-all-5.1.1-bin-release.zip +``` + +Prepare a terminal and change to the extracted `bin` directory: +```shell +$ cd rocketmq-all-5.1.1/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 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 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... +``` + +For Windows users: +```shell +$ mqbroker.cmd -n localhost:9876 +The broker[broker-a, 192.169.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 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 is 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 is 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) -* [RocketMQ Flink](https://github.com/apache/rocketmq-flink) -* [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) -* [RocketMQ Exporter](https://github.com/apache/rocketmq-exporter) -* [RocketMQ Operator](https://github.com/apache/rocketmq-operator) -* [RocketMQ Docker](https://github.com/apache/rocketmq-docker) -* [RocketMQ Incubating Community Projects](https://github.com/apache/rocketmq-externals) +* [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 make it easier to build a event-driven application. +* [RocketMQ Incubating Community Projects](https://github.com/apache/rocketmq-externals): Icubator 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 @@ -56,7 +197,7 @@ It offers a variety of features: * Rips: * Ask: * Slack: - + ---------- @@ -64,7 +205,7 @@ It offers a variety of features: ## 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 @@ -90,3 +231,20 @@ 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://www.apache.org/licenses/LICENSE-2.0.html +[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 +[pencentage-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 diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 00000000000..26633f0d4b6 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,146 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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", + "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.1", + "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:1.30", + "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.0.2", + "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.19.0", + "io.opentelemetry:opentelemetry-exporter-prometheus:1.19.0-alpha", + "io.opentelemetry:opentelemetry-exporter-logging:1.19.0", + "io.opentelemetry:opentelemetry-sdk:1.19.0", + "com.squareup.okio:okio-jvm:3.0.0", + "io.opentelemetry:opentelemetry-api:1.19.0", + "io.opentelemetry:opentelemetry-sdk-metrics:1.19.0", + "io.opentelemetry:opentelemetry-sdk-common:1.19.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", + ], + 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 = "a2a5cccec251211e2221b1587af2ce43c36d32a42f5d881737db3b546a536510", + strip_prefix = "buildbuddy-toolchain-829c8a574f706de5c96c54ca310f139f4acda7dd", + urls = ["https://github.com/buildbuddy-io/buildbuddy-toolchain/archive/829c8a574f706de5c96c54ca310f139f4acda7dd.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 = "rbe_default", + # The sha256 digest of the tarball might change without notice. So it's not + # included here. + urls = ["https://storage.googleapis.com/rbe-toolchain/bazel-configs/rbe-ubuntu1604/latest/rbe_default.tar"], +) + +http_archive( + name = "bazel_toolchains", + sha256 = "56d5370eb99559b4c74f334f81bc8a298f728bd16d5a4333c865c2ad10fae3bc", + strip_prefix = "bazel-toolchains-dac71231098d891e5c4b74a2078fe9343feef510", + urls = ["https://github.com/bazelbuild/bazel-toolchains/archive/dac71231098d891e5c4b74a2078fe9343feef510.tar.gz"], +) + +load("@bazel_toolchains//repositories:repositories.bzl", bazel_toolchains_repositories = "repositories") + +bazel_toolchains_repositories() diff --git a/acl/BUILD.bazel b/acl/BUILD.bazel new file mode 100644 index 00000000000..ac6ac65c779 --- /dev/null +++ b/acl/BUILD.bazel @@ -0,0 +1,75 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 = "acl", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//common", + "//remoting", + "//srvutil", + "@maven//:com_alibaba_fastjson", + "@maven//:com_github_luben_zstd_jni", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:commons_codec_commons_codec", + "@maven//:commons_validator_commons_validator", + "@maven//:io_netty_netty_all", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:org_apache_rocketmq_rocketmq_proto", + "@maven//:org_lz4_lz4_java", + "@maven//:org_yaml_snakeyaml", + "@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 = glob(["src/test/resources/**/*.yml"]), + visibility = ["//visibility:public"], + deps = [ + ":acl", + "//:test_deps", + "//common", + "//remoting", + "@maven//:com_alibaba_fastjson", + "@maven//:com_google_guava_guava", + "@maven//:commons_codec_commons_codec", + "@maven//:io_netty_netty_all", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:org_springframework_spring_core", + "@maven//:org_yaml_snakeyaml", + ], +) + +GenTestRules( + name = "GeneratedTestRules", + # The following tests are not hermetic. Fix them later. + exclude_tests = [ + ], + medium_tests = [ + "src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest", + ], + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], +) diff --git a/acl/pom.xml b/acl/pom.xml index 2c5cac2d9e0..26c30d1350c 100644 --- a/acl/pom.xml +++ b/acl/pom.xml @@ -13,19 +13,23 @@ org.apache.rocketmq rocketmq-all - 4.9.4-SNAPSHOT + 5.1.3 rocketmq-acl rocketmq-acl ${project.version} + + ${basedir}/.. + + ${project.groupId} - rocketmq-remoting + rocketmq-proto ${project.groupId} - rocketmq-logging + rocketmq-remoting ${project.groupId} @@ -35,6 +39,14 @@ ${project.groupId} rocketmq-srvutil + + io.github.aliyunmq + rocketmq-slf4j-api + + + io.github.aliyunmq + rocketmq-logback-classic + org.yaml snakeyaml @@ -47,21 +59,19 @@ org.apache.commons commons-lang3 - - org.slf4j - slf4j-api - test + commons-validator + commons-validator - ch.qos.logback - logback-classic - test + com.google.protobuf + protobuf-java-util + - commons-validator - commons-validator + org.springframework + spring-core + test - diff --git a/acl/src/main/java/org/apache/rocketmq/acl/AccessValidator.java b/acl/src/main/java/org/apache/rocketmq/acl/AccessValidator.java index 167fa26e886..315184c6150 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/AccessValidator.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/AccessValidator.java @@ -17,12 +17,13 @@ package org.apache.rocketmq.acl; +import com.google.protobuf.GeneratedMessageV3; import java.util.List; import java.util.Map; - +import org.apache.rocketmq.acl.common.AuthenticationHeader; import org.apache.rocketmq.common.AclConfig; -import org.apache.rocketmq.common.DataVersion; import org.apache.rocketmq.common.PlainAccessConfig; +import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public interface AccessValidator { @@ -36,6 +37,14 @@ public interface AccessValidator { */ AccessResource parse(RemotingCommand request, String remoteAddr); + /** + * Parse to get the AccessResource from gRPC protocol + * @param messageV3 + * @param header + * @return Plain access resource + */ + AccessResource parse(GeneratedMessageV3 messageV3, AuthenticationHeader header); + /** * Validate the access resource. * @@ -56,7 +65,7 @@ public interface AccessValidator { * * @return */ - boolean deleteAccessConfig(String accesskey); + boolean deleteAccessConfig(String accessKey); /** * Get the access resource config version information @@ -73,6 +82,8 @@ public interface AccessValidator { */ boolean updateGlobalWhiteAddrsConfig(List globalWhiteAddrsList); + boolean updateGlobalWhiteAddrsConfig(List globalWhiteAddrsList, String aclFileFullPath); + /** * get broker cluster acl config information * diff --git a/acl/src/main/java/org/apache/rocketmq/acl/PermissionChecker.java b/acl/src/main/java/org/apache/rocketmq/acl/PermissionChecker.java new file mode 100644 index 00000000000..a38d3ec4787 --- /dev/null +++ b/acl/src/main/java/org/apache/rocketmq/acl/PermissionChecker.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +public interface PermissionChecker { + void check(AccessResource checkedAccess, AccessResource ownedAccess); +} diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/AclClientRPCHook.java b/acl/src/main/java/org/apache/rocketmq/acl/common/AclClientRPCHook.java index 9e5bf1fb5d9..294db4f069a 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/common/AclClientRPCHook.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/common/AclClientRPCHook.java @@ -16,11 +16,9 @@ */ package org.apache.rocketmq.acl.common; -import java.lang.reflect.Field; +import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; -import java.util.concurrent.ConcurrentHashMap; -import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; @@ -30,8 +28,6 @@ public class AclClientRPCHook implements RPCHook { private final SessionCredentials sessionCredentials; - protected ConcurrentHashMap, Field[]> fieldCache = - new ConcurrentHashMap, Field[]>(); public AclClientRPCHook(SessionCredentials sessionCredentials) { this.sessionCredentials = sessionCredentials; @@ -39,16 +35,15 @@ public AclClientRPCHook(SessionCredentials sessionCredentials) { @Override public void doBeforeRequest(String remoteAddr, RemotingCommand request) { - byte[] total = AclUtils.combineRequestContent(request, - parseRequestContent(request, sessionCredentials.getAccessKey(), sessionCredentials.getSecurityToken())); - String signature = AclUtils.calSignature(total, sessionCredentials.getSecretKey()); - request.addExtField(SIGNATURE, signature); + // Add AccessKey and SecurityToken into signature calculating. request.addExtField(ACCESS_KEY, sessionCredentials.getAccessKey()); - - // The SecurityToken value is unneccessary,user can choose this one. + // The SecurityToken value is unnecessary,user can choose this one. if (sessionCredentials.getSecurityToken() != null) { request.addExtField(SECURITY_TOKEN, sessionCredentials.getSecurityToken()); } + byte[] total = AclUtils.combineRequestContent(request, parseRequestContent(request)); + String signature = AclUtils.calSignature(total, sessionCredentials.getSecretKey()); + request.addExtField(SIGNATURE, signature); } @Override @@ -56,40 +51,11 @@ public void doAfterResponse(String remoteAddr, RemotingCommand request, Remoting } - protected SortedMap parseRequestContent(RemotingCommand request, String ak, String securityToken) { - CommandCustomHeader header = request.readCustomHeader(); + protected SortedMap parseRequestContent(RemotingCommand request) { + request.makeCustomHeaderToNet(); + Map extFields = request.getExtFields(); // 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); - } + return new TreeMap<>(extFields); } public SessionCredentials getSessionCredentials() { diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/AclSigner.java b/acl/src/main/java/org/apache/rocketmq/acl/common/AclSigner.java index 61e9350663f..b4baa897225 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/common/AclSigner.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/common/AclSigner.java @@ -17,17 +17,18 @@ package org.apache.rocketmq.acl.common; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class AclSigner { - public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; public static final SigningAlgorithm DEFAULT_ALGORITHM = SigningAlgorithm.HmacSHA1; - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.ROCKETMQ_AUTHORIZE_LOGGER_NAME); + 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"; diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/AclUtils.java b/acl/src/main/java/org/apache/rocketmq/acl/common/AclUtils.java index b385338c6d3..65f04f54339 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/common/AclUtils.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/common/AclUtils.java @@ -16,19 +16,18 @@ */ package org.apache.rocketmq.acl.common; -import com.alibaba.fastjson.JSONObject; -import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileWriter; -import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.util.Map; import java.util.SortedMap; + +import com.alibaba.fastjson.JSONObject; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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; @@ -37,7 +36,7 @@ public class AclUtils { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); public static byte[] combineRequestContent(RemotingCommand request, SortedMap fieldsMap) { try { @@ -55,12 +54,11 @@ public static byte[] combineRequestContent(RemotingCommand request, SortedMap -1 && asterisk != netaddress.length() - 1) { - throw new AclException(String.format("Netaddress examine scope Exception netaddress is %s", netaddress)); + 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)); + 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)); + 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) { + public static String v6ipProcess(String netAddress) { int part; String subAddress; - boolean isAsterisk = isAsterisk(netaddress); - boolean isMinus = isMinus(netaddress); + 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); + int lastColon = netAddress.lastIndexOf(':'); + int secondLastColon = netAddress.substring(0, lastColon).lastIndexOf(':'); + subAddress = netAddress.substring(0, secondLastColon); } else if (!isAsterisk && !isMinus) { part = 8; - subAddress = netaddress; + subAddress = netAddress; } else { part = 7; - subAddress = netaddress.substring(0, netaddress.lastIndexOf(':')); + 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 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) { + 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[] addreeStrArray = new String[parAddStrArray.length]; + String address = netAddress.substring(0, netAddress.indexOf("{")); + String[] addressStrArray = new String[parAddStrArray.length]; for (int i = 0; i < parAddStrArray.length; i++) { - addreeStrArray[i] = address + parAddStrArray[i]; + addressStrArray[i] = address + parAddStrArray[i]; } - return addreeStrArray; + return addressStrArray; } - public static boolean isScope(String netaddress, int index) { + public static boolean isScope(String netAddress, int index) { // IPv6 Address - if (isColon(netaddress)) { - netaddress = expandIP(netaddress, 8); - String[] strArray = StringUtils.split(netaddress, ":"); + if (isColon(netAddress)) { + netAddress = expandIP(netAddress, 8); + String[] strArray = StringUtils.split(netAddress, ":"); return isIPv6Scope(strArray, index); } - String[] strArray = StringUtils.split(netaddress, "."); + String[] strArray = StringUtils.split(netAddress, "."); if (strArray.length != 4) { return false; } @@ -146,20 +144,16 @@ public static boolean isScope(String netaddress, int index) { } public static boolean isScope(String[] num, int index) { - if (num.length <= 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 isColon(String netAddress) { + return netAddress.indexOf(':') > -1; } public static boolean isScope(String num) { @@ -204,21 +198,21 @@ public static boolean isIPv6Scope(int num) { return num >= min && num <= max; } - public static String expandIP(String netaddress, int part) { - netaddress = netaddress.toUpperCase(); - // expand netaddress - int separatorCount = StringUtils.countMatches(netaddress, ":"); + 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()); + netAddress = StringUtils.replace(netAddress, "::", padStr.toString()); } - // pad netaddress - String[] strArray = StringUtils.splitPreserveAllTokens(netaddress, ":"); + // 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'); @@ -237,55 +231,62 @@ public static String expandIP(String netaddress, int part) { } public static T getYamlDataObject(String path, Class clazz) { - Yaml yaml = new Yaml(); - FileInputStream fis = null; - try { - fis = new FileInputStream(new File(path)); - return yaml.loadAs(fis, clazz); + try (FileInputStream fis = new FileInputStream(path)) { + return getYamlDataObject(fis, clazz); } catch (FileNotFoundException ignore) { return null; } catch (Exception e) { - throw new AclException(e.getMessage()); - } finally { - if (fis != null) { - try { - fis.close(); - } catch (IOException ignore) { - } - } + throw new AclException(e.getMessage(), e); } } - public static boolean writeDataObject(String path, Map dataMap) { + public static T getYamlDataObject(InputStream fis, Class clazz) { Yaml yaml = new Yaml(); - PrintWriter pw = null; try { - pw = new PrintWriter(new FileWriter(path)); + return yaml.loadAs(fis, clazz); + } catch (Exception e) { + throw new AclException(e.getMessage(), e); + } + } + + public static boolean writeDataObject(String path, Object dataMap) { + Yaml yaml = new Yaml(); + try (PrintWriter pw = new PrintWriter(path, "UTF-8")) { String dumpAsMap = yaml.dumpAsMap(dataMap); pw.print(dumpAsMap); pw.flush(); } catch (Exception e) { - throw new AclException(e.getMessage()); - } finally { - if (pw != null) { - pw.close(); - } + throw new AclException(e.getMessage(), e); } return true; } public static RPCHook getAclRPCHook(String fileName) { - JSONObject yamlDataObject = null; + JSONObject yamlDataObject; try { yamlDataObject = AclUtils.getYamlDataObject(fileName, - JSONObject.class); + 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("Cannot find conf file :{}, acl isn't be enabled.", fileName); + log.warn("Failed to parse configuration to enable ACL."); return null; } @@ -293,8 +294,7 @@ public static RPCHook getAclRPCHook(String fileName) { String secretKey = yamlDataObject.getString(AclConstants.CONFIG_SECRET_KEY); if (StringUtils.isBlank(accessKey) || StringUtils.isBlank(secretKey)) { - log.warn("AccessKey or secretKey is blank, the acl is not enabled."); - + log.warn("Failed to enable ACL. Either AccessKey or secretKey is blank"); return null; } return new AclClientRPCHook(new SessionCredentials(accessKey, secretKey)); diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/AuthenticationHeader.java b/acl/src/main/java/org/apache/rocketmq/acl/common/AuthenticationHeader.java new file mode 100644 index 00000000000..5b00c00c787 --- /dev/null +++ b/acl/src/main/java/org/apache/rocketmq/acl/common/AuthenticationHeader.java @@ -0,0 +1,237 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.google.common.base.MoreObjects; + +public class AuthenticationHeader { + private String remoteAddress; + private String tenantId; + private String namespace; + private String authorization; + private String datetime; + private String sessionToken; + private String requestId; + private String language; + private String clientVersion; + private String protocol; + private int requestCode; + + AuthenticationHeader(final String remoteAddress, final String tenantId, final String namespace, + final String authorization, final String datetime, final String sessionToken, final String requestId, + final String language, final String clientVersion, final String protocol, final int requestCode) { + this.remoteAddress = remoteAddress; + this.tenantId = tenantId; + this.namespace = namespace; + this.authorization = authorization; + this.datetime = datetime; + this.sessionToken = sessionToken; + this.requestId = requestId; + this.language = language; + this.clientVersion = clientVersion; + this.protocol = protocol; + this.requestCode = requestCode; + } + + public static class MetadataHeaderBuilder { + private String remoteAddress; + private String tenantId; + private String namespace; + private String authorization; + private String datetime; + private String sessionToken; + private String requestId; + private String language; + private String clientVersion; + private String protocol; + private int requestCode; + + MetadataHeaderBuilder() { + } + + public AuthenticationHeader.MetadataHeaderBuilder remoteAddress(final String remoteAddress) { + this.remoteAddress = remoteAddress; + return this; + } + + public AuthenticationHeader.MetadataHeaderBuilder tenantId(final String tenantId) { + this.tenantId = tenantId; + return this; + } + + public AuthenticationHeader.MetadataHeaderBuilder namespace(final String namespace) { + this.namespace = namespace; + return this; + } + + public AuthenticationHeader.MetadataHeaderBuilder authorization(final String authorization) { + this.authorization = authorization; + return this; + } + + public AuthenticationHeader.MetadataHeaderBuilder datetime(final String datetime) { + this.datetime = datetime; + return this; + } + + public AuthenticationHeader.MetadataHeaderBuilder sessionToken(final String sessionToken) { + this.sessionToken = sessionToken; + return this; + } + + public AuthenticationHeader.MetadataHeaderBuilder requestId(final String requestId) { + this.requestId = requestId; + return this; + } + + public AuthenticationHeader.MetadataHeaderBuilder language(final String language) { + this.language = language; + return this; + } + + public AuthenticationHeader.MetadataHeaderBuilder clientVersion(final String clientVersion) { + this.clientVersion = clientVersion; + return this; + } + + public AuthenticationHeader.MetadataHeaderBuilder protocol(final String protocol) { + this.protocol = protocol; + return this; + } + + public AuthenticationHeader.MetadataHeaderBuilder requestCode(final int requestCode) { + this.requestCode = requestCode; + return this; + } + + public AuthenticationHeader build() { + return new AuthenticationHeader(this.remoteAddress, this.tenantId, this.namespace, this.authorization, + this.datetime, this.sessionToken, this.requestId, this.language, this.clientVersion, this.protocol, + this.requestCode); + } + } + + public static AuthenticationHeader.MetadataHeaderBuilder builder() { + return new AuthenticationHeader.MetadataHeaderBuilder(); + } + + public String getRemoteAddress() { + return this.remoteAddress; + } + + public String getTenantId() { + return this.tenantId; + } + + public String getNamespace() { + return this.namespace; + } + + public String getAuthorization() { + return this.authorization; + } + + public String getDatetime() { + return this.datetime; + } + + public String getSessionToken() { + return this.sessionToken; + } + + public String getRequestId() { + return this.requestId; + } + + public String getLanguage() { + return this.language; + } + + public String getClientVersion() { + return this.clientVersion; + } + + public String getProtocol() { + return this.protocol; + } + + public int getRequestCode() { + return this.requestCode; + } + + public void setRemoteAddress(final String remoteAddress) { + this.remoteAddress = remoteAddress; + } + + public void setTenantId(final String tenantId) { + this.tenantId = tenantId; + } + + public void setNamespace(final String namespace) { + this.namespace = namespace; + } + + public void setAuthorization(final String authorization) { + this.authorization = authorization; + } + + public void setDatetime(final String datetime) { + this.datetime = datetime; + } + + public void setSessionToken(final String sessionToken) { + this.sessionToken = sessionToken; + } + + public void setRequestId(final String requestId) { + this.requestId = requestId; + } + + public void setLanguage(final String language) { + this.language = language; + } + + public void setClientVersion(final String clientVersion) { + this.clientVersion = clientVersion; + } + + public void setProtocol(final String protocol) { + this.protocol = protocol; + } + + public void setRequestCode(int requestCode) { + this.requestCode = requestCode; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("remoteAddress", remoteAddress) + .add("tenantId", tenantId) + .add("namespace", namespace) + .add("authorization", authorization) + .add("datetime", datetime) + .add("sessionToken", sessionToken) + .add("requestId", requestId) + .add("language", language) + .add("clientVersion", clientVersion) + .add("protocol", protocol) + .add("requestCode", requestCode) + .toString(); + } +} diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/AuthorizationHeader.java b/acl/src/main/java/org/apache/rocketmq/acl/common/AuthorizationHeader.java new file mode 100644 index 00000000000..eb75aa3bee9 --- /dev/null +++ b/acl/src/main/java/org/apache/rocketmq/acl/common/AuthorizationHeader.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.google.common.base.MoreObjects; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; + +public class AuthorizationHeader { + private static final String HEADER_SEPARATOR = " "; + private static final String CREDENTIALS_SEPARATOR = "/"; + private static final int AUTH_HEADER_KV_LENGTH = 2; + private static final String CREDENTIAL = "Credential"; + private static final String SIGNED_HEADERS = "SignedHeaders"; + private static final String SIGNATURE = "Signature"; + private String method; + private String accessKey; + private String[] signedHeaders; + private String signature; + + /** + * Parse authorization from gRPC header. + * + * @param header gRPC header string. + * @throws Exception exception. + */ + public AuthorizationHeader(String header) throws DecoderException { + String[] result = header.split(HEADER_SEPARATOR, 2); + if (result.length != 2) { + throw new DecoderException("authorization header is incorrect"); + } + this.method = result[0]; + String[] keyValues = result[1].split(","); + for (String keyValue : keyValues) { + String[] kv = keyValue.trim().split("=", 2); + int kvLength = kv.length; + if (kv.length != AUTH_HEADER_KV_LENGTH) { + throw new DecoderException("authorization keyValues length is incorrect, actual length=" + kvLength); + } + String authItem = kv[0]; + if (CREDENTIAL.equals(authItem)) { + String[] credential = kv[1].split(CREDENTIALS_SEPARATOR); + int credentialActualLength = credential.length; + if (credentialActualLength == 0) { + throw new DecoderException("authorization credential length is incorrect, actual length=" + credentialActualLength); + } + this.accessKey = credential[0]; + continue; + } + if (SIGNED_HEADERS.equals(authItem)) { + this.signedHeaders = kv[1].split(";"); + continue; + } + if (SIGNATURE.equals(authItem)) { + this.signature = this.hexToBase64(kv[1]); + } + } + } + + public String hexToBase64(String input) throws DecoderException { + byte[] bytes = Hex.decodeHex(input); + return Base64.encodeBase64String(bytes); + } + + public String getMethod() { + return this.method; + } + + public String getAccessKey() { + return this.accessKey; + } + + public String[] getSignedHeaders() { + return this.signedHeaders; + } + + public String getSignature() { + return this.signature; + } + + public void setMethod(final String method) { + this.method = method; + } + + public void setAccessKey(final String accessKey) { + this.accessKey = accessKey; + } + + public void setSignedHeaders(final String[] signedHeaders) { + this.signedHeaders = signedHeaders; + } + + public void setSignature(final String signature) { + this.signature = signature; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("method", method) + .add("accessKey", accessKey) + .add("signedHeaders", signedHeaders) + .add("signature", signature) + .toString(); + } +} diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/Permission.java b/acl/src/main/java/org/apache/rocketmq/acl/common/Permission.java index dadcaa304aa..38649b08327 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/common/Permission.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/common/Permission.java @@ -21,7 +21,7 @@ import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.acl.plain.PlainAccessResource; -import org.apache.rocketmq.common.protocol.RequestCode; +import org.apache.rocketmq.remoting.protocol.RequestCode; public class Permission { @@ -30,7 +30,7 @@ public class Permission { public static final byte PUB = 1 << 2; public static final byte SUB = 1 << 3; - public static final Set ADMIN_CODE = new HashSet(); + public static final Set ADMIN_CODE = new HashSet<>(); static { // UPDATE_AND_CREATE_TOPIC @@ -50,7 +50,7 @@ public static boolean checkPermission(byte neededPerm, byte ownedPerm) { return false; } if ((neededPerm & ANY) > 0) { - return ((ownedPerm & PUB) > 0) || ((ownedPerm & SUB) > 0); + return (ownedPerm & PUB) > 0 || (ownedPerm & SUB) > 0; } return (neededPerm & ownedPerm) > 0; } diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/SessionCredentials.java b/acl/src/main/java/org/apache/rocketmq/acl/common/SessionCredentials.java index 33a8a34350c..dfc06d4f3a4 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/common/SessionCredentials.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/common/SessionCredentials.java @@ -19,11 +19,12 @@ import java.io.File; import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Properties; import org.apache.rocketmq.common.MixAll; public class SessionCredentials { - public static final Charset CHARSET = Charset.forName("UTF-8"); + 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"; @@ -147,12 +148,8 @@ public boolean equals(Object obj) { return false; if (signature == null) { - if (other.signature != null) - return false; - } else if (!signature.equals(other.signature)) - return false; - - return true; + return other.signature == null; + } else return signature.equals(other.signature); } @Override diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessData.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessData.java new file mode 100644 index 00000000000..83c8cc40c49 --- /dev/null +++ b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessData.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.plain; + +import org.apache.rocketmq.common.PlainAccessConfig; + +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); + } +} diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessResource.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessResource.java index a0cceed8c31..cdbd9ea9b36 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessResource.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessResource.java @@ -16,11 +16,52 @@ */ package org.apache.rocketmq.acl.plain; +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.NotifyClientTerminationRequest; +import apache.rocketmq.v2.QueryAssignmentRequest; +import apache.rocketmq.v2.QueryRouteRequest; +import apache.rocketmq.v2.ReceiveMessageRequest; +import apache.rocketmq.v2.Resource; +import apache.rocketmq.v2.SendMessageRequest; +import apache.rocketmq.v2.Subscription; +import apache.rocketmq.v2.SubscriptionEntry; +import apache.rocketmq.v2.TelemetryCommand; +import com.google.protobuf.GeneratedMessageV3; +import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import org.apache.commons.codec.DecoderException; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.rocketmq.acl.AccessResource; +import org.apache.rocketmq.acl.common.AclException; +import org.apache.rocketmq.acl.common.AclUtils; +import org.apache.rocketmq.acl.common.AuthenticationHeader; +import org.apache.rocketmq.acl.common.AuthorizationHeader; +import org.apache.rocketmq.acl.common.Permission; +import org.apache.rocketmq.acl.common.SessionCredentials; +import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; +import org.apache.rocketmq.common.PlainAccessConfig; +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.ResponseCode; +import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader; +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 PlainAccessResource implements AccessResource { @@ -55,6 +96,227 @@ public class PlainAccessResource implements AccessResource { public PlainAccessResource() { } + public static PlainAccessResource parse(RemotingCommand request, String remoteAddr) { + PlainAccessResource accessResource = new PlainAccessResource(); + if (remoteAddr != null && remoteAddr.contains(":")) { + accessResource.setWhiteRemoteAddress(remoteAddr.substring(0, remoteAddr.lastIndexOf(':'))); + } else { + accessResource.setWhiteRemoteAddress(remoteAddr); + } + + accessResource.setRequestCode(request.getCode()); + + if (request.getExtFields() == null) { + // If request's extFields is null,then return accessResource directly(users can use whiteAddress pattern) + // The following logic codes depend on the request's extFields not to be null. + return accessResource; + } + accessResource.setAccessKey(request.getExtFields().get(SessionCredentials.ACCESS_KEY)); + accessResource.setSignature(request.getExtFields().get(SessionCredentials.SIGNATURE)); + accessResource.setSecretToken(request.getExtFields().get(SessionCredentials.SECURITY_TOKEN)); + + try { + switch (request.getCode()) { + case RequestCode.SEND_MESSAGE: + final String topic = request.getExtFields().get("topic"); + if (PlainAccessResource.isRetryTopic(topic)) { + accessResource.addResourceAndPerm(getRetryTopic(request.getExtFields().get("group")), Permission.SUB); + } else { + accessResource.addResourceAndPerm(topic, Permission.PUB); + } + break; + case RequestCode.SEND_MESSAGE_V2: + case RequestCode.SEND_BATCH_MESSAGE: + final String topicV2 = request.getExtFields().get("b"); + if (PlainAccessResource.isRetryTopic(topicV2)) { + accessResource.addResourceAndPerm(getRetryTopic(request.getExtFields().get("a")), Permission.SUB); + } else { + accessResource.addResourceAndPerm(topicV2, Permission.PUB); + } + break; + case RequestCode.CONSUMER_SEND_MSG_BACK: + accessResource.addResourceAndPerm(getRetryTopic(request.getExtFields().get("group")), Permission.SUB); + break; + case RequestCode.PULL_MESSAGE: + accessResource.addResourceAndPerm(request.getExtFields().get("topic"), Permission.SUB); + accessResource.addResourceAndPerm(getRetryTopic(request.getExtFields().get("consumerGroup")), Permission.SUB); + break; + case RequestCode.QUERY_MESSAGE: + accessResource.addResourceAndPerm(request.getExtFields().get("topic"), Permission.SUB); + break; + case RequestCode.HEART_BEAT: + HeartbeatData heartbeatData = HeartbeatData.decode(request.getBody(), HeartbeatData.class); + for (ConsumerData data : heartbeatData.getConsumerDataSet()) { + accessResource.addResourceAndPerm(getRetryTopic(data.getGroupName()), Permission.SUB); + for (SubscriptionData subscriptionData : data.getSubscriptionDataSet()) { + accessResource.addResourceAndPerm(subscriptionData.getTopic(), Permission.SUB); + } + } + break; + case RequestCode.UNREGISTER_CLIENT: + final UnregisterClientRequestHeader unregisterClientRequestHeader = + (UnregisterClientRequestHeader) request + .decodeCommandCustomHeader(UnregisterClientRequestHeader.class); + accessResource.addResourceAndPerm(getRetryTopic(unregisterClientRequestHeader.getConsumerGroup()), Permission.SUB); + break; + case RequestCode.GET_CONSUMER_LIST_BY_GROUP: + final GetConsumerListByGroupRequestHeader getConsumerListByGroupRequestHeader = + (GetConsumerListByGroupRequestHeader) request + .decodeCommandCustomHeader(GetConsumerListByGroupRequestHeader.class); + accessResource.addResourceAndPerm(getRetryTopic(getConsumerListByGroupRequestHeader.getConsumerGroup()), Permission.SUB); + break; + case RequestCode.UPDATE_CONSUMER_OFFSET: + final UpdateConsumerOffsetRequestHeader updateConsumerOffsetRequestHeader = + (UpdateConsumerOffsetRequestHeader) request + .decodeCommandCustomHeader(UpdateConsumerOffsetRequestHeader.class); + accessResource.addResourceAndPerm(getRetryTopic(updateConsumerOffsetRequestHeader.getConsumerGroup()), Permission.SUB); + accessResource.addResourceAndPerm(updateConsumerOffsetRequestHeader.getTopic(), Permission.SUB); + break; + default: + break; + + } + } catch (Throwable t) { + throw new AclException(t.getMessage(), t); + } + + // Content + SortedMap map = new TreeMap<>(); + for (Map.Entry entry : request.getExtFields().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()); + } + } + accessResource.setContent(AclUtils.combineRequestContent(request, map)); + return accessResource; + } + + public static PlainAccessResource parse(GeneratedMessageV3 messageV3, AuthenticationHeader header) { + PlainAccessResource accessResource = new PlainAccessResource(); + String remoteAddress = header.getRemoteAddress(); + if (remoteAddress != null && remoteAddress.contains(":")) { + accessResource.setWhiteRemoteAddress(RemotingHelper.parseHostFromAddress(remoteAddress)); + } else { + accessResource.setWhiteRemoteAddress(remoteAddress); + } + try { + AuthorizationHeader authorizationHeader = new AuthorizationHeader(header.getAuthorization()); + accessResource.setAccessKey(authorizationHeader.getAccessKey()); + accessResource.setSignature(authorizationHeader.getSignature()); + } catch (DecoderException e) { + throw new AclException(e.getMessage(), e); + } + accessResource.setSecretToken(header.getSessionToken()); + accessResource.setRequestCode(header.getRequestCode()); + accessResource.setContent(header.getDatetime().getBytes(StandardCharsets.UTF_8)); + + try { + String rpcFullName = messageV3.getDescriptorForType().getFullName(); + if (HeartbeatRequest.getDescriptor().getFullName().equals(rpcFullName)) { + HeartbeatRequest request = (HeartbeatRequest) messageV3; + if (ClientType.PUSH_CONSUMER.equals(request.getClientType()) + || ClientType.SIMPLE_CONSUMER.equals(request.getClientType())) { + if (!request.hasGroup()) { + throw new AclException("Consumer heartbeat doesn't have group"); + } else { + accessResource.addResourceAndPerm(request.getGroup(), Permission.SUB); + } + } + } else if (SendMessageRequest.getDescriptor().getFullName().equals(rpcFullName)) { + SendMessageRequest request = (SendMessageRequest) messageV3; + if (request.getMessagesCount() <= 0) { + throw new AclException("SendMessageRequest, messageCount is zero", ResponseCode.MESSAGE_ILLEGAL); + } + Resource topic = request.getMessages(0).getTopic(); + for (Message message : request.getMessagesList()) { + if (!message.getTopic().equals(topic)) { + throw new AclException("SendMessageRequest, messages' topic is not consistent", ResponseCode.MESSAGE_ILLEGAL); + } + } + accessResource.addResourceAndPerm(topic, Permission.PUB); + } else if (ReceiveMessageRequest.getDescriptor().getFullName().equals(rpcFullName)) { + ReceiveMessageRequest request = (ReceiveMessageRequest) messageV3; + accessResource.addResourceAndPerm(request.getGroup(), Permission.SUB); + accessResource.addResourceAndPerm(request.getMessageQueue().getTopic(), Permission.SUB); + } else if (AckMessageRequest.getDescriptor().getFullName().equals(rpcFullName)) { + AckMessageRequest request = (AckMessageRequest) messageV3; + accessResource.addResourceAndPerm(request.getGroup(), Permission.SUB); + accessResource.addResourceAndPerm(request.getTopic(), Permission.SUB); + } else if (ForwardMessageToDeadLetterQueueRequest.getDescriptor().getFullName().equals(rpcFullName)) { + ForwardMessageToDeadLetterQueueRequest request = (ForwardMessageToDeadLetterQueueRequest) messageV3; + accessResource.addResourceAndPerm(request.getGroup(), Permission.SUB); + accessResource.addResourceAndPerm(request.getTopic(), Permission.SUB); + } else if (EndTransactionRequest.getDescriptor().getFullName().equals(rpcFullName)) { + EndTransactionRequest request = (EndTransactionRequest) messageV3; + accessResource.addResourceAndPerm(request.getTopic(), Permission.PUB); + } else if (TelemetryCommand.getDescriptor().getFullName().equals(rpcFullName)) { + TelemetryCommand command = (TelemetryCommand) messageV3; + if (command.getCommandCase() == TelemetryCommand.CommandCase.SETTINGS) { + if (command.getSettings().hasPublishing()) { + List topicList = command.getSettings().getPublishing().getTopicsList(); + for (Resource topic : topicList) { + accessResource.addResourceAndPerm(topic, Permission.PUB); + } + } + if (command.getSettings().hasSubscription()) { + Subscription subscription = command.getSettings().getSubscription(); + accessResource.addResourceAndPerm(subscription.getGroup(), Permission.SUB); + for (SubscriptionEntry entry : subscription.getSubscriptionsList()) { + accessResource.addResourceAndPerm(entry.getTopic(), Permission.SUB); + } + } + if (!command.getSettings().hasPublishing() && !command.getSettings().hasSubscription()) { + throw new AclException("settings command doesn't have publishing or subscription"); + } + } + } else if (NotifyClientTerminationRequest.getDescriptor().getFullName().equals(rpcFullName)) { + NotifyClientTerminationRequest request = (NotifyClientTerminationRequest) messageV3; + accessResource.addResourceAndPerm(request.getGroup(), Permission.SUB); + } else if (QueryRouteRequest.getDescriptor().getFullName().equals(rpcFullName)) { + QueryRouteRequest request = (QueryRouteRequest) messageV3; + accessResource.addResourceAndPerm(request.getTopic(), Permission.ANY); + } else if (QueryAssignmentRequest.getDescriptor().getFullName().equals(rpcFullName)) { + QueryAssignmentRequest request = (QueryAssignmentRequest) messageV3; + accessResource.addResourceAndPerm(request.getGroup(), Permission.SUB); + accessResource.addResourceAndPerm(request.getTopic(), Permission.SUB); + } else if (ChangeInvisibleDurationRequest.getDescriptor().getFullName().equals(rpcFullName)) { + ChangeInvisibleDurationRequest request = (ChangeInvisibleDurationRequest) messageV3; + accessResource.addResourceAndPerm(request.getGroup(), Permission.SUB); + accessResource.addResourceAndPerm(request.getTopic(), Permission.SUB); + } + } catch (Throwable t) { + throw new AclException(t.getMessage(), t); + } + return accessResource; + } + + private void addResourceAndPerm(Resource resource, byte permission) { + String resourceName = NamespaceUtil.wrapNamespace(resource.getResourceNamespace(), resource.getName()); + addResourceAndPerm(resourceName, permission); + } + + public static PlainAccessResource build(PlainAccessConfig plainAccessConfig, RemoteAddressStrategy remoteAddressStrategy) { + PlainAccessResource plainAccessResource = new PlainAccessResource(); + plainAccessResource.setAccessKey(plainAccessConfig.getAccessKey()); + plainAccessResource.setSecretKey(plainAccessConfig.getSecretKey()); + plainAccessResource.setWhiteRemoteAddress(plainAccessConfig.getWhiteRemoteAddress()); + + plainAccessResource.setAdmin(plainAccessConfig.isAdmin()); + + plainAccessResource.setDefaultGroupPerm(Permission.parsePermFromString(plainAccessConfig.getDefaultGroupPerm())); + plainAccessResource.setDefaultTopicPerm(Permission.parsePermFromString(plainAccessConfig.getDefaultTopicPerm())); + + Permission.parseResourcePerms(plainAccessResource, false, plainAccessConfig.getGroupPerms()); + Permission.parseResourcePerms(plainAccessResource, true, plainAccessConfig.getTopicPerms()); + + plainAccessResource.setRemoteAddressStrategy(remoteAddressStrategy); + return plainAccessResource; + } + public static boolean isRetryTopic(String topic) { return null != topic && topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX); } diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessValidator.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessValidator.java index 98858cfc965..a7015eaca73 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessValidator.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessValidator.java @@ -16,31 +16,17 @@ */ package org.apache.rocketmq.acl.plain; +import com.google.protobuf.GeneratedMessageV3; import java.util.List; import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; import org.apache.rocketmq.acl.AccessResource; import org.apache.rocketmq.acl.AccessValidator; -import org.apache.rocketmq.acl.common.AclException; -import org.apache.rocketmq.acl.common.AclUtils; -import org.apache.rocketmq.acl.common.Permission; -import org.apache.rocketmq.acl.common.SessionCredentials; +import org.apache.rocketmq.acl.common.AuthenticationHeader; import org.apache.rocketmq.common.AclConfig; -import org.apache.rocketmq.common.DataVersion; -import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.PlainAccessConfig; -import org.apache.rocketmq.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupRequestHeader; -import org.apache.rocketmq.common.protocol.header.UnregisterClientRequestHeader; -import org.apache.rocketmq.common.protocol.header.UpdateConsumerOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData; -import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; +import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingCommand; -import static org.apache.rocketmq.acl.plain.PlainAccessResource.getRetryTopic; - public class PlainAccessValidator implements AccessValidator { private PlainPermissionManager aclPlugEngine; @@ -51,89 +37,12 @@ public PlainAccessValidator() { @Override public AccessResource parse(RemotingCommand request, String remoteAddr) { - PlainAccessResource accessResource = new PlainAccessResource(); - if (remoteAddr != null && remoteAddr.contains(":")) { - accessResource.setWhiteRemoteAddress(remoteAddr.substring(0, remoteAddr.lastIndexOf(':'))); - } else { - accessResource.setWhiteRemoteAddress(remoteAddr); - } - - accessResource.setRequestCode(request.getCode()); - - if (request.getExtFields() == null) { - // If request's extFields is null,then return accessResource directly(users can use whiteAddress pattern) - // The following logic codes depend on the request's extFields not to be null. - return accessResource; - } - accessResource.setAccessKey(request.getExtFields().get(SessionCredentials.ACCESS_KEY)); - accessResource.setSignature(request.getExtFields().get(SessionCredentials.SIGNATURE)); - accessResource.setSecretToken(request.getExtFields().get(SessionCredentials.SECURITY_TOKEN)); - - try { - switch (request.getCode()) { - case RequestCode.SEND_MESSAGE: - accessResource.addResourceAndPerm(request.getExtFields().get("topic"), Permission.PUB); - break; - case RequestCode.SEND_MESSAGE_V2: - accessResource.addResourceAndPerm(request.getExtFields().get("b"), Permission.PUB); - break; - case RequestCode.CONSUMER_SEND_MSG_BACK: - accessResource.addResourceAndPerm(request.getExtFields().get("originTopic"), Permission.PUB); - accessResource.addResourceAndPerm(getRetryTopic(request.getExtFields().get("group")), Permission.SUB); - break; - case RequestCode.PULL_MESSAGE: - accessResource.addResourceAndPerm(request.getExtFields().get("topic"), Permission.SUB); - accessResource.addResourceAndPerm(getRetryTopic(request.getExtFields().get("consumerGroup")), Permission.SUB); - break; - case RequestCode.QUERY_MESSAGE: - accessResource.addResourceAndPerm(request.getExtFields().get("topic"), Permission.SUB); - break; - case RequestCode.HEART_BEAT: - HeartbeatData heartbeatData = HeartbeatData.decode(request.getBody(), HeartbeatData.class); - for (ConsumerData data : heartbeatData.getConsumerDataSet()) { - accessResource.addResourceAndPerm(getRetryTopic(data.getGroupName()), Permission.SUB); - for (SubscriptionData subscriptionData : data.getSubscriptionDataSet()) { - accessResource.addResourceAndPerm(subscriptionData.getTopic(), Permission.SUB); - } - } - break; - case RequestCode.UNREGISTER_CLIENT: - final UnregisterClientRequestHeader unregisterClientRequestHeader = - (UnregisterClientRequestHeader) request - .decodeCommandCustomHeader(UnregisterClientRequestHeader.class); - accessResource.addResourceAndPerm(getRetryTopic(unregisterClientRequestHeader.getConsumerGroup()), Permission.SUB); - break; - case RequestCode.GET_CONSUMER_LIST_BY_GROUP: - final GetConsumerListByGroupRequestHeader getConsumerListByGroupRequestHeader = - (GetConsumerListByGroupRequestHeader) request - .decodeCommandCustomHeader(GetConsumerListByGroupRequestHeader.class); - accessResource.addResourceAndPerm(getRetryTopic(getConsumerListByGroupRequestHeader.getConsumerGroup()), Permission.SUB); - break; - case RequestCode.UPDATE_CONSUMER_OFFSET: - final UpdateConsumerOffsetRequestHeader updateConsumerOffsetRequestHeader = - (UpdateConsumerOffsetRequestHeader) request - .decodeCommandCustomHeader(UpdateConsumerOffsetRequestHeader.class); - accessResource.addResourceAndPerm(getRetryTopic(updateConsumerOffsetRequestHeader.getConsumerGroup()), Permission.SUB); - accessResource.addResourceAndPerm(updateConsumerOffsetRequestHeader.getTopic(), Permission.SUB); - break; - default: - break; - - } - } catch (Throwable t) { - throw new AclException(t.getMessage(), t); - } + return PlainAccessResource.parse(request, remoteAddr); + } - // Content - SortedMap map = new TreeMap(); - for (Map.Entry entry : request.getExtFields().entrySet()) { - if (!SessionCredentials.SIGNATURE.equals(entry.getKey()) - && !MixAll.UNIQUE_MSG_QUERY_FLAG.equals(entry.getKey())) { - map.put(entry.getKey(), entry.getValue()); - } - } - accessResource.setContent(AclUtils.combineRequestContent(request, map)); - return accessResource; + @Override + public AccessResource parse(GeneratedMessageV3 messageV3, AuthenticationHeader header) { + return PlainAccessResource.parse(messageV3, header); } @Override @@ -147,29 +56,28 @@ public boolean updateAccessConfig(PlainAccessConfig plainAccessConfig) { } @Override - public boolean deleteAccessConfig(String accesskey) { - return aclPlugEngine.deleteAccessConfig(accesskey); + public boolean deleteAccessConfig(String accessKey) { + return aclPlugEngine.deleteAccessConfig(accessKey); } - @Override public String getAclConfigVersion() { + @Override + public String getAclConfigVersion() { return aclPlugEngine.getAclConfigDataVersion(); } - @Override public boolean updateGlobalWhiteAddrsConfig(List globalWhiteAddrsList) { + @Override + public boolean updateGlobalWhiteAddrsConfig(List globalWhiteAddrsList) { return aclPlugEngine.updateGlobalWhiteAddrsConfig(globalWhiteAddrsList); } - @Override public AclConfig getAllAclConfig() { - return aclPlugEngine.getAllAclConfig(); - } - - public Map createAclAccessConfigMap(Map existedAccountMap, - PlainAccessConfig plainAccessConfig) { - return aclPlugEngine.createAclAccessConfigMap(existedAccountMap, plainAccessConfig); + @Override + public boolean updateGlobalWhiteAddrsConfig(List globalWhiteAddrsList, String aclFileFullPath) { + return aclPlugEngine.updateGlobalWhiteAddrsConfig(globalWhiteAddrsList, aclFileFullPath); } - public Map updateAclConfigFileVersion(Map updateAclConfigMap) { - return aclPlugEngine.updateAclConfigFileVersion(updateAclConfigMap); + @Override + public AclConfig getAllAclConfig() { + return aclPlugEngine.getAllAclConfig(); } @Override diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionChecker.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionChecker.java new file mode 100644 index 00000000000..8e6c317b237 --- /dev/null +++ b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionChecker.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.plain; + +import java.util.Map; +import org.apache.rocketmq.acl.AccessResource; +import org.apache.rocketmq.acl.PermissionChecker; +import org.apache.rocketmq.acl.common.AclException; +import org.apache.rocketmq.acl.common.Permission; + +public class PlainPermissionChecker implements PermissionChecker { + public void check(AccessResource checkedAccess, AccessResource ownedAccess) { + PlainAccessResource checkedPlainAccess = (PlainAccessResource) checkedAccess; + PlainAccessResource ownedPlainAccess = (PlainAccessResource) ownedAccess; + + if (ownedPlainAccess.isAdmin()) { + // admin user don't need verification + return; + } + if (Permission.needAdminPerm(checkedPlainAccess.getRequestCode())) { + throw new AclException(String.format("Need admin permission for request code=%d, but accessKey=%s is not", checkedPlainAccess.getRequestCode(), ownedPlainAccess.getAccessKey())); + } + + Map needCheckedPermMap = checkedPlainAccess.getResourcePermMap(); + Map ownedPermMap = ownedPlainAccess.getResourcePermMap(); + + if (needCheckedPermMap == null) { + // If the needCheckedPermMap is null,then return + return; + } + + for (Map.Entry needCheckedEntry : needCheckedPermMap.entrySet()) { + String resource = needCheckedEntry.getKey(); + Byte neededPerm = needCheckedEntry.getValue(); + boolean isGroup = PlainAccessResource.isRetryTopic(resource); + + if (ownedPermMap == null || !ownedPermMap.containsKey(resource)) { + // Check the default perm + byte ownedPerm = isGroup ? ownedPlainAccess.getDefaultGroupPerm() : + ownedPlainAccess.getDefaultTopicPerm(); + if (!Permission.checkPermission(neededPerm, ownedPerm)) { + throw new AclException(String.format("No default permission for %s", PlainAccessResource.printStr(resource, isGroup))); + } + continue; + } + if (!Permission.checkPermission(neededPerm, ownedPermMap.get(resource))) { + throw new AclException(String.format("No permission for %s", PlainAccessResource.printStr(resource, isGroup))); + } + } + } +} diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java index 7f2936a0dad..f6699fa13bc 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java @@ -16,45 +16,47 @@ */ package org.apache.rocketmq.acl.plain; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - 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.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; - import org.apache.commons.lang3.StringUtils; +import org.apache.rocketmq.acl.PermissionChecker; import org.apache.rocketmq.acl.common.AclConstants; import org.apache.rocketmq.acl.common.AclException; import org.apache.rocketmq.acl.common.AclUtils; import org.apache.rocketmq.acl.common.Permission; import org.apache.rocketmq.common.AclConfig; -import org.apache.rocketmq.common.DataVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.PlainAccessConfig; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.srvutil.AclFileWatchService; public class PlainPermissionManager { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private String fileHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV)); - private String defaultAclDir = fileHome + File.separator + "conf" + File.separator + "acl"; + private String defaultAclDir; - private String defaultAclFile = fileHome + File.separator + System.getProperty("rocketmq.acl.plain.file", "conf/plain_acl.yml"); + private String defaultAclFile; private Map> aclPlainAccessResourceMap = new HashMap<>(); @@ -75,7 +77,11 @@ public class PlainPermissionManager { private List fileList = new ArrayList<>(); + private final PermissionChecker permissionChecker = new PlainPermissionChecker(); + 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(); watch(); } @@ -85,7 +91,7 @@ public List getAllAclFiles(String path) { log.info("The default acl dir {} is not exist", path); return new ArrayList<>(); } - List allAclFileFullPath = new ArrayList<>(); + List allAclFileFullPath = new ArrayList<>(); File file = new File(path); File[] files = file.listFiles(); for (int i = 0; i < files.length; i++) { @@ -113,59 +119,63 @@ public void load() { Map> globalWhiteRemoteAddressStrategyMap = new HashMap<>(); Map dataVersionMap = new HashMap<>(); + assureAclConfigFilesExist(); + fileList = getAllAclFiles(defaultAclDir); if (new File(defaultAclFile).exists() && !fileList.contains(defaultAclFile)) { fileList.add(defaultAclFile); } for (int i = 0; i < fileList.size(); i++) { - JSONObject plainAclConfData = AclUtils.getYamlDataObject(fileList.get(i), - JSONObject.class); - if (plainAclConfData == null || plainAclConfData.isEmpty()) { - throw new AclException(String.format("%s file is not data", fileList.get(i))); + final String currentFile = MixAll.dealFilePath(fileList.get(i)); + PlainAccessData plainAclConfData = AclUtils.getYamlDataObject(currentFile, + PlainAccessData.class); + if (plainAclConfData == null) { + log.warn("No data in file {}", currentFile); + continue; } - log.info("Broker plain acl conf data is : ", plainAclConfData.toString()); + log.info("Broker plain acl conf data is : {}", plainAclConfData.toString()); List globalWhiteRemoteAddressStrategyList = new ArrayList<>(); - JSONArray globalWhiteRemoteAddressesList = plainAclConfData.getJSONArray("globalWhiteRemoteAddresses"); + List globalWhiteRemoteAddressesList = plainAclConfData.getGlobalWhiteRemoteAddresses(); if (globalWhiteRemoteAddressesList != null && !globalWhiteRemoteAddressesList.isEmpty()) { for (int j = 0; j < globalWhiteRemoteAddressesList.size(); j++) { globalWhiteRemoteAddressStrategyList.add(remoteAddressStrategyFactory. - getRemoteAddressStrategy(globalWhiteRemoteAddressesList.getString(j))); + getRemoteAddressStrategy(globalWhiteRemoteAddressesList.get(j))); } } if (globalWhiteRemoteAddressStrategyList.size() > 0) { - globalWhiteRemoteAddressStrategyMap.put(fileList.get(i), globalWhiteRemoteAddressStrategyList); + globalWhiteRemoteAddressStrategyMap.put(currentFile, globalWhiteRemoteAddressStrategyList); globalWhiteRemoteAddressStrategy.addAll(globalWhiteRemoteAddressStrategyList); } - JSONArray accounts = plainAclConfData.getJSONArray(AclConstants.CONFIG_ACCOUNTS); + List accounts = plainAclConfData.getAccounts(); Map plainAccessResourceMap = new HashMap<>(); if (accounts != null && !accounts.isEmpty()) { - List plainAccessConfigList = accounts.toJavaList(PlainAccessConfig.class); - for (PlainAccessConfig plainAccessConfig : plainAccessConfigList) { + for (PlainAccessConfig plainAccessConfig : accounts) { PlainAccessResource plainAccessResource = buildPlainAccessResource(plainAccessConfig); //AccessKey can not be defined in multiple ACL files if (accessKeyTable.get(plainAccessResource.getAccessKey()) == null) { plainAccessResourceMap.put(plainAccessResource.getAccessKey(), plainAccessResource); - accessKeyTable.put(plainAccessResource.getAccessKey(), fileList.get(i)); + accessKeyTable.put(plainAccessResource.getAccessKey(), currentFile); } else { - log.warn("The accesssKey {} is repeated in multiple ACL files", plainAccessResource.getAccessKey()); + log.warn("The accessKey {} is repeated in multiple ACL files", plainAccessResource.getAccessKey()); } } } if (plainAccessResourceMap.size() > 0) { - aclPlainAccessResourceMap.put(fileList.get(i), plainAccessResourceMap); + aclPlainAccessResourceMap.put(currentFile, plainAccessResourceMap); } - JSONArray tempDataVersion = plainAclConfData.getJSONArray(AclConstants.CONFIG_DATA_VERSION); + List dataVersions = plainAclConfData.getDataVersion(); DataVersion dataVersion = new DataVersion(); - if (tempDataVersion != null && !tempDataVersion.isEmpty()) { - List dataVersions = tempDataVersion.toJavaList(DataVersion.class); - DataVersion firstElement = dataVersions.get(0); + if (dataVersions != null && !dataVersions.isEmpty()) { + DataVersion firstElement = new DataVersion(); + firstElement.setCounter(new AtomicLong(dataVersions.get(0).getCounter())); + firstElement.setTimestamp(dataVersions.get(0).getTimestamp()); dataVersion.assignNewOne(firstElement); } - dataVersionMap.put(fileList.get(i), dataVersion); + dataVersionMap.put(currentFile, dataVersion); } if (dataVersionMap.containsKey(defaultAclFile)) { @@ -178,21 +188,40 @@ public void load() { this.accessKeyTable = accessKeyTable; } + /** + * 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 void load(String aclFilePath) { + aclFilePath = MixAll.dealFilePath(aclFilePath); Map plainAccessResourceMap = new HashMap<>(); List globalWhiteRemoteAddressStrategy = new ArrayList<>(); - JSONObject plainAclConfData = AclUtils.getYamlDataObject(aclFilePath, - JSONObject.class); - if (plainAclConfData == null || plainAclConfData.isEmpty()) { - throw new AclException(String.format("%s file is not data", aclFilePath)); + PlainAccessData plainAclConfData = AclUtils.getYamlDataObject(aclFilePath, + PlainAccessData.class); + if (plainAclConfData == null) { + log.warn("No data in {}, skip it", aclFilePath); + return; } - log.info("Broker plain acl conf data is : ", plainAclConfData.toString()); - JSONArray globalWhiteRemoteAddressesList = plainAclConfData.getJSONArray("globalWhiteRemoteAddresses"); + log.info("Broker plain acl conf data is : {}", plainAclConfData.toString()); + List globalWhiteRemoteAddressesList = plainAclConfData.getGlobalWhiteRemoteAddresses(); if (globalWhiteRemoteAddressesList != null && !globalWhiteRemoteAddressesList.isEmpty()) { for (int i = 0; i < globalWhiteRemoteAddressesList.size(); i++) { globalWhiteRemoteAddressStrategy.add(remoteAddressStrategyFactory. - getRemoteAddressStrategy(globalWhiteRemoteAddressesList.getString(i))); + getRemoteAddressStrategy(globalWhiteRemoteAddressesList.get(i))); } } @@ -205,26 +234,28 @@ public void load(String aclFilePath) { this.globalWhiteRemoteAddressStrategyMap.put(aclFilePath, globalWhiteRemoteAddressStrategy); } - - JSONArray accounts = plainAclConfData.getJSONArray(AclConstants.CONFIG_ACCOUNTS); + List accounts = plainAclConfData.getAccounts(); if (accounts != null && !accounts.isEmpty()) { - List plainAccessConfigList = accounts.toJavaList(PlainAccessConfig.class); - for (PlainAccessConfig plainAccessConfig : plainAccessConfigList) { + for (PlainAccessConfig plainAccessConfig : accounts) { PlainAccessResource plainAccessResource = buildPlainAccessResource(plainAccessConfig); //AccessKey can not be defined in multiple ACL files - if (this.accessKeyTable.get(plainAccessResource.getAccessKey()) == null) { + String oldPath = this.accessKeyTable.get(plainAccessResource.getAccessKey()); + if (oldPath == null || aclFilePath.equals(oldPath)) { plainAccessResourceMap.put(plainAccessResource.getAccessKey(), plainAccessResource); this.accessKeyTable.put(plainAccessResource.getAccessKey(), aclFilePath); + } else { + log.warn("The accessKey {} is repeated in multiple ACL files", plainAccessResource.getAccessKey()); } } } // For loading dataversion part just - JSONArray tempDataVersion = plainAclConfData.getJSONArray(AclConstants.CONFIG_DATA_VERSION); + List dataVersions = plainAclConfData.getDataVersion(); DataVersion dataVersion = new DataVersion(); - if (tempDataVersion != null && !tempDataVersion.isEmpty()) { - List dataVersions = tempDataVersion.toJavaList(DataVersion.class); - DataVersion firstElement = dataVersions.get(0); + if (dataVersions != null && !dataVersions.isEmpty()) { + DataVersion firstElement = new DataVersion(); + firstElement.setCounter(new AtomicLong(dataVersions.get(0).getCounter())); + firstElement.setTimestamp(dataVersions.get(0).getTimestamp()); dataVersion.assignNewOne(firstElement); } @@ -235,7 +266,6 @@ public void load(String aclFilePath) { } } - @Deprecated public String getAclConfigDataVersion() { return this.dataVersion.toJson(); @@ -245,30 +275,26 @@ public Map getDataVersionMap() { return this.dataVersionMap; } - public Map updateAclConfigFileVersion(Map updateAclConfigMap) { + public PlainAccessData updateAclConfigFileVersion(String aclFileName, PlainAccessData updateAclConfigMap) { - Object dataVersions = updateAclConfigMap.get(AclConstants.CONFIG_DATA_VERSION); + List dataVersions = updateAclConfigMap.getDataVersion(); DataVersion dataVersion = new DataVersion(); if (dataVersions != null) { - List> dataVersionList = (List>) dataVersions; - if (dataVersionList.size() > 0) { - dataVersion.setTimestamp((long) dataVersionList.get(0).get("timestamp")); - dataVersion.setCounter(new AtomicLong(Long.parseLong(dataVersionList.get(0).get("counter").toString()))); + if (dataVersions.size() > 0) { + dataVersion.setTimestamp(dataVersions.get(0).getTimestamp()); + dataVersion.setCounter(new AtomicLong(dataVersions.get(0).getCounter())); } } dataVersion.nextVersion(); - List> versionElement = new ArrayList>(); - Map accountsMap = new LinkedHashMap(); - accountsMap.put(AclConstants.CONFIG_COUNTER, dataVersion.getCounter().longValue()); - accountsMap.put(AclConstants.CONFIG_TIME_STAMP, dataVersion.getTimestamp()); - - versionElement.add(accountsMap); - updateAclConfigMap.put(AclConstants.CONFIG_DATA_VERSION, versionElement); + List versionElement = new ArrayList<>(); + PlainAccessData.DataVersion dataVersionNew = new PlainAccessData.DataVersion(); + dataVersionNew.setTimestamp(dataVersion.getTimestamp()); + dataVersionNew.setCounter(dataVersion.getCounter().get()); + versionElement.add(dataVersionNew); + updateAclConfigMap.setDataVersion(versionElement); - List> accounts = (List>) updateAclConfigMap.get(AclConstants.CONFIG_ACCOUNTS); - String accessKey = (String) accounts.get(0).get(AclConstants.CONFIG_ACCESS_KEY); - String aclFileName = accessKeyTable.get(accessKey); dataVersionMap.put(aclFileName, dataVersion); + return updateAclConfigMap; } @@ -284,29 +310,37 @@ public boolean updateAccessConfig(PlainAccessConfig plainAccessConfig) { Permission.checkResourcePerms(plainAccessConfig.getGroupPerms()); if (accessKeyTable.containsKey(plainAccessConfig.getAccessKey())) { - Map updateAccountMap = null; + PlainAccessConfig updateAccountMap = null; String aclFileName = accessKeyTable.get(plainAccessConfig.getAccessKey()); - Map aclAccessConfigMap = AclUtils.getYamlDataObject(aclFileName, Map.class); - List> accounts = (List>) aclAccessConfigMap.get(AclConstants.CONFIG_ACCOUNTS); - for (Map account : accounts) { - if (account.get(AclConstants.CONFIG_ACCESS_KEY).equals(plainAccessConfig.getAccessKey())) { - // Update acl access config elements - accounts.remove(account); - updateAccountMap = createAclAccessConfigMap(account, plainAccessConfig); - accounts.add(updateAccountMap); - aclAccessConfigMap.put(AclConstants.CONFIG_ACCOUNTS, accounts); - break; + PlainAccessData aclAccessConfigMap = AclUtils.getYamlDataObject(aclFileName, PlainAccessData.class); + List accounts = aclAccessConfigMap.getAccounts(); + if (null != accounts) { + for (PlainAccessConfig account : accounts) { + if (account.getAccessKey().equals(plainAccessConfig.getAccessKey())) { + // Update acl access config elements + accounts.remove(account); + updateAccountMap = createAclAccessConfigMap(account, plainAccessConfig); + accounts.add(updateAccountMap); + aclAccessConfigMap.setAccounts(accounts); + break; + } } + } else { + // Maybe deleted in file, add it back + accounts = new LinkedList<>(); + updateAccountMap = createAclAccessConfigMap(null, plainAccessConfig); + accounts.add(updateAccountMap); + aclAccessConfigMap.setAccounts(accounts); } Map accountMap = aclPlainAccessResourceMap.get(aclFileName); if (accountMap == null) { - accountMap = new HashMap(1); + accountMap = new HashMap<>(1); accountMap.put(plainAccessConfig.getAccessKey(), buildPlainAccessResource(plainAccessConfig)); } else if (accountMap.size() == 0) { accountMap.put(plainAccessConfig.getAccessKey(), buildPlainAccessResource(plainAccessConfig)); } else { for (Map.Entry entry : accountMap.entrySet()) { - if (entry.getValue().equals(plainAccessConfig.getAccessKey())) { + if (entry.getValue().getAccessKey().equals(plainAccessConfig.getAccessKey())) { PlainAccessResource plainAccessResource = buildPlainAccessResource(plainAccessConfig); accountMap.put(entry.getKey(), plainAccessResource); break; @@ -314,9 +348,9 @@ public boolean updateAccessConfig(PlainAccessConfig plainAccessConfig) { } } aclPlainAccessResourceMap.put(aclFileName, accountMap); - return AclUtils.writeDataObject(aclFileName, updateAclConfigFileVersion(aclAccessConfigMap)); + return AclUtils.writeDataObject(aclFileName, updateAclConfigFileVersion(aclFileName, aclAccessConfigMap)); } else { - String fileName = defaultAclFile; + String fileName = MixAll.dealFilePath(defaultAclFile); //Create acl access config elements on the default acl file if (aclPlainAccessResourceMap.get(defaultAclFile) == null || aclPlainAccessResourceMap.get(defaultAclFile).size() == 0) { try { @@ -328,14 +362,17 @@ public boolean updateAccessConfig(PlainAccessConfig plainAccessConfig) { log.warn("create default acl file has exception when update accessConfig. ", e); } } - Map aclAccessConfigMap = AclUtils.getYamlDataObject(defaultAclFile, Map.class); + PlainAccessData aclAccessConfigMap = AclUtils.getYamlDataObject(defaultAclFile, PlainAccessData.class); if (aclAccessConfigMap == null) { - aclAccessConfigMap = new HashMap<>(); - aclAccessConfigMap.put(AclConstants.CONFIG_ACCOUNTS, new ArrayList<>()); + aclAccessConfigMap = new PlainAccessData(); + } + List accounts = aclAccessConfigMap.getAccounts(); + // When no accounts defined + if (null == accounts) { + accounts = new ArrayList<>(); } - List> accounts = (List>) aclAccessConfigMap.get(AclConstants.CONFIG_ACCOUNTS); accounts.add(createAclAccessConfigMap(null, plainAccessConfig)); - aclAccessConfigMap.put(AclConstants.CONFIG_ACCOUNTS, accounts); + aclAccessConfigMap.setAccounts(accounts); accessKeyTable.put(plainAccessConfig.getAccessKey(), fileName); if (aclPlainAccessResourceMap.get(fileName) == null) { Map plainAccessResourceMap = new HashMap<>(1); @@ -346,16 +383,16 @@ public boolean updateAccessConfig(PlainAccessConfig plainAccessConfig) { plainAccessResourceMap.put(plainAccessConfig.getAccessKey(), buildPlainAccessResource(plainAccessConfig)); aclPlainAccessResourceMap.put(fileName, plainAccessResourceMap); } - return AclUtils.writeDataObject(defaultAclFile, updateAclConfigFileVersion(aclAccessConfigMap)); + return AclUtils.writeDataObject(defaultAclFile, updateAclConfigFileVersion(defaultAclFile, aclAccessConfigMap)); } } - public Map createAclAccessConfigMap(Map existedAccountMap, + public PlainAccessConfig createAclAccessConfigMap(PlainAccessConfig existedAccountMap, PlainAccessConfig plainAccessConfig) { - Map newAccountsMap = null; + PlainAccessConfig newAccountsMap = null; if (existedAccountMap == null) { - newAccountsMap = new LinkedHashMap(); + newAccountsMap = new PlainAccessConfig(); } else { newAccountsMap = existedAccountMap; } @@ -366,7 +403,7 @@ public Map createAclAccessConfigMap(Map existedA "The accessKey=%s cannot be null and length should longer than 6", plainAccessConfig.getAccessKey())); } - newAccountsMap.put(AclConstants.CONFIG_ACCESS_KEY, plainAccessConfig.getAccessKey()); + newAccountsMap.setAccessKey(plainAccessConfig.getAccessKey()); if (!StringUtils.isEmpty(plainAccessConfig.getSecretKey())) { if (plainAccessConfig.getSecretKey().length() <= AclConstants.SECRET_KEY_MIN_LENGTH) { @@ -374,51 +411,53 @@ public Map createAclAccessConfigMap(Map existedA "The secretKey=%s value length should longer than 6", plainAccessConfig.getSecretKey())); } - newAccountsMap.put(AclConstants.CONFIG_SECRET_KEY, plainAccessConfig.getSecretKey()); + newAccountsMap.setSecretKey(plainAccessConfig.getSecretKey()); } if (plainAccessConfig.getWhiteRemoteAddress() != null) { - newAccountsMap.put(AclConstants.CONFIG_WHITE_ADDR, plainAccessConfig.getWhiteRemoteAddress()); + newAccountsMap.setWhiteRemoteAddress(plainAccessConfig.getWhiteRemoteAddress()); } if (!StringUtils.isEmpty(String.valueOf(plainAccessConfig.isAdmin()))) { - newAccountsMap.put(AclConstants.CONFIG_ADMIN_ROLE, plainAccessConfig.isAdmin()); + newAccountsMap.setAdmin(plainAccessConfig.isAdmin()); } if (!StringUtils.isEmpty(plainAccessConfig.getDefaultTopicPerm())) { - newAccountsMap.put(AclConstants.CONFIG_DEFAULT_TOPIC_PERM, plainAccessConfig.getDefaultTopicPerm()); + newAccountsMap.setDefaultTopicPerm(plainAccessConfig.getDefaultTopicPerm()); } if (!StringUtils.isEmpty(plainAccessConfig.getDefaultGroupPerm())) { - newAccountsMap.put(AclConstants.CONFIG_DEFAULT_GROUP_PERM, plainAccessConfig.getDefaultGroupPerm()); + newAccountsMap.setDefaultGroupPerm(plainAccessConfig.getDefaultGroupPerm()); } if (plainAccessConfig.getTopicPerms() != null) { - newAccountsMap.put(AclConstants.CONFIG_TOPIC_PERMS, plainAccessConfig.getTopicPerms()); + newAccountsMap.setTopicPerms(plainAccessConfig.getTopicPerms()); } if (plainAccessConfig.getGroupPerms() != null) { - newAccountsMap.put(AclConstants.CONFIG_GROUP_PERMS, plainAccessConfig.getGroupPerms()); + newAccountsMap.setGroupPerms(plainAccessConfig.getGroupPerms()); } return newAccountsMap; } - public boolean deleteAccessConfig(String accesskey) { - if (StringUtils.isEmpty(accesskey)) { - log.error("Parameter value accesskey is null or empty String,Please check your parameter"); + public boolean deleteAccessConfig(String accessKey) { + if (StringUtils.isEmpty(accessKey)) { + log.error("Parameter value accessKey is null or empty String,Please check your parameter"); return false; } - if (accessKeyTable.containsKey(accesskey)) { - String aclFileName = accessKeyTable.get(accesskey); - Map aclAccessConfigMap = AclUtils.getYamlDataObject(aclFileName, - Map.class); - if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) { - throw new AclException(String.format("the %s file is not found or empty", aclFileName)); + if (accessKeyTable.containsKey(accessKey)) { + String aclFileName = accessKeyTable.get(accessKey); + PlainAccessData aclAccessConfigData = AclUtils.getYamlDataObject(aclFileName, + PlainAccessData.class); + if (aclAccessConfigData == null) { + log.warn("No data found in {} when deleting access config of {}", aclFileName, accessKey); + return true; } - List> accounts = (List>) aclAccessConfigMap.get("accounts"); - Iterator> itemIterator = accounts.iterator(); + List accounts = aclAccessConfigData.getAccounts(); + Iterator itemIterator = accounts.iterator(); while (itemIterator.hasNext()) { - if (itemIterator.next().get(AclConstants.CONFIG_ACCESS_KEY).equals(accesskey)) { + if (itemIterator.next().getAccessKey().equals(accessKey)) { // Delete the related acl config element itemIterator.remove(); - aclAccessConfigMap.put(AclConstants.CONFIG_ACCOUNTS, accounts); - return AclUtils.writeDataObject(aclFileName, updateAclConfigFileVersion(aclAccessConfigMap)); + accessKeyTable.remove(accessKey); + aclAccessConfigData.setAccounts(accounts); + return AclUtils.writeDataObject(aclFileName, updateAclConfigFileVersion(aclFileName, aclAccessConfigData)); } } } @@ -426,33 +465,14 @@ public boolean deleteAccessConfig(String accesskey) { } public boolean updateGlobalWhiteAddrsConfig(List globalWhiteAddrsList) { - - if (globalWhiteAddrsList == null) { - log.error("Parameter value globalWhiteAddrsList is null,Please check your parameter"); - return false; - } - - Map aclAccessConfigMap = AclUtils.getYamlDataObject(defaultAclFile, Map.class); - if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) { - throw new AclException(String.format("the %s file is not found or empty", defaultAclFile)); - } - List globalWhiteRemoteAddrList = (List) aclAccessConfigMap.get(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS); - - if (globalWhiteRemoteAddrList != null) { - globalWhiteRemoteAddrList.clear(); - if (globalWhiteAddrsList != null) { - globalWhiteRemoteAddrList.addAll(globalWhiteAddrsList); - } - // Update globalWhiteRemoteAddr element in memory map firstly - aclAccessConfigMap.put(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS, globalWhiteRemoteAddrList); - return AclUtils.writeDataObject(defaultAclFile, updateAclConfigFileVersion(aclAccessConfigMap)); - } - - log.error("Users must ensure that the acl yaml config file has globalWhiteRemoteAddresses flag in the {} firstly", defaultAclFile); - return false; + return this.updateGlobalWhiteAddrsConfig(globalWhiteAddrsList, this.defaultAclFile); } public boolean updateGlobalWhiteAddrsConfig(List globalWhiteAddrsList, String fileName) { + if (fileName == null || fileName.equals("")) { + fileName = this.defaultAclFile; + } + if (globalWhiteAddrsList == null) { log.error("Parameter value globalWhiteAddrsList is null,Please check your parameter"); return false; @@ -460,27 +480,29 @@ public boolean updateGlobalWhiteAddrsConfig(List globalWhiteAddrsList, S File file = new File(fileName); if (!file.exists() || file.isDirectory()) { - log.error("Parameter value fileName is not exist or is a directory,Please check your parameter"); + log.error("Parameter value " + fileName + " is not exist or is a directory, please check your parameter"); return false; } - Map aclAccessConfigMap = AclUtils.getYamlDataObject(fileName, Map.class); - if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) { - throw new AclException(String.format("the %s file is not found or empty", fileName)); + if (!fileName.startsWith(fileHome)) { + log.error("Parameter value " + fileName + " is not in the directory rocketmq.home.dir " + fileHome); + return false; } - List globalWhiteRemoteAddrList = (List) aclAccessConfigMap.get(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS); - if (globalWhiteRemoteAddrList != null) { - globalWhiteRemoteAddrList.clear(); - if (globalWhiteAddrsList != null) { - globalWhiteRemoteAddrList.addAll(globalWhiteAddrsList); - } - // Update globalWhiteRemoteAddr element in memory map firstly - aclAccessConfigMap.put(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS, globalWhiteRemoteAddrList); - return AclUtils.writeDataObject(fileName, updateAclConfigFileVersion(aclAccessConfigMap)); + + if (!fileName.endsWith(".yml") && fileName.endsWith(".yaml")) { + log.error("Parameter value " + fileName + " is not a ACL configuration file"); + return false; } - log.error("Users must ensure that the acl yaml config file has globalWhiteRemoteAddresses flag in the {} firstly", fileName); - return false; + PlainAccessData aclAccessConfigMap = AclUtils.getYamlDataObject(fileName, PlainAccessData.class); + if (aclAccessConfigMap == null) { + aclAccessConfigMap = new PlainAccessData(); + log.info("No data in {}, create a new aclAccessConfigMap", fileName); + } + // Update globalWhiteRemoteAddr element in memory map firstly + aclAccessConfigMap.setGlobalWhiteRemoteAddresses(new ArrayList<>(globalWhiteAddrsList)); + return AclUtils.writeDataObject(fileName, updateAclConfigFileVersion(fileName, aclAccessConfigMap)); + } public AclConfig getAllAclConfig() { @@ -491,19 +513,18 @@ public AclConfig getAllAclConfig() { for (int i = 0; i < fileList.size(); i++) { String path = fileList.get(i); - JSONObject plainAclConfData = AclUtils.getYamlDataObject(path, - JSONObject.class); - if (plainAclConfData == null || plainAclConfData.isEmpty()) { - throw new AclException(String.format("%s file is not data", path)); + PlainAccessData plainAclConfData = AclUtils.getYamlDataObject(path, + PlainAccessData.class); + if (plainAclConfData == null) { + continue; } - JSONArray globalWhiteAddrs = plainAclConfData.getJSONArray(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS); + List globalWhiteAddrs = plainAclConfData.getGlobalWhiteRemoteAddresses(); if (globalWhiteAddrs != null && !globalWhiteAddrs.isEmpty()) { - whiteAddrs.addAll(globalWhiteAddrs.toJavaList(String.class)); + whiteAddrs.addAll(globalWhiteAddrs); } - JSONArray accounts = plainAclConfData.getJSONArray(AclConstants.CONFIG_ACCOUNTS); - if (accounts != null && !accounts.isEmpty()) { - List plainAccessConfigs = accounts.toJavaList(PlainAccessConfig.class); + List plainAccessConfigs = plainAclConfData.getAccounts(); + if (plainAccessConfigs != null && !plainAccessConfigs.isEmpty()) { for (int j = 0; j < plainAccessConfigs.size(); j++) { if (!accessKeySets.contains(plainAccessConfigs.get(j).getAccessKey())) { accessKeySets.add(plainAccessConfigs.get(j).getAccessKey()); @@ -549,40 +570,7 @@ public void onFileNumChanged(String path) { } void checkPerm(PlainAccessResource needCheckedAccess, PlainAccessResource ownedAccess) { - if (Permission.needAdminPerm(needCheckedAccess.getRequestCode()) && !ownedAccess.isAdmin()) { - throw new AclException(String.format("Need admin permission for request code=%d, but accessKey=%s is not", needCheckedAccess.getRequestCode(), ownedAccess.getAccessKey())); - } - Map needCheckedPermMap = needCheckedAccess.getResourcePermMap(); - Map ownedPermMap = ownedAccess.getResourcePermMap(); - - if (needCheckedPermMap == null) { - // If the needCheckedPermMap is null,then return - return; - } - - if (ownedPermMap == null && ownedAccess.isAdmin()) { - // If the ownedPermMap is null and it is an admin user, then return - return; - } - - for (Map.Entry needCheckedEntry : needCheckedPermMap.entrySet()) { - String resource = needCheckedEntry.getKey(); - Byte neededPerm = needCheckedEntry.getValue(); - boolean isGroup = PlainAccessResource.isRetryTopic(resource); - - if (ownedPermMap == null || !ownedPermMap.containsKey(resource)) { - // Check the default perm - byte ownedPerm = isGroup ? ownedAccess.getDefaultGroupPerm() : - ownedAccess.getDefaultTopicPerm(); - if (!Permission.checkPermission(neededPerm, ownedPerm)) { - throw new AclException(String.format("No default permission for %s", PlainAccessResource.printStr(resource, isGroup))); - } - continue; - } - if (!Permission.checkPermission(neededPerm, ownedPermMap.get(resource))) { - throw new AclException(String.format("No default permission for %s", PlainAccessResource.printStr(resource, isGroup))); - } - } + permissionChecker.check(needCheckedAccess, ownedAccess); } void clearPermissionInfo() { @@ -604,23 +592,8 @@ public void checkPlainAccessConfig(PlainAccessConfig plainAccessConfig) throws A public PlainAccessResource buildPlainAccessResource(PlainAccessConfig plainAccessConfig) throws AclException { checkPlainAccessConfig(plainAccessConfig); - PlainAccessResource plainAccessResource = new PlainAccessResource(); - plainAccessResource.setAccessKey(plainAccessConfig.getAccessKey()); - plainAccessResource.setSecretKey(plainAccessConfig.getSecretKey()); - plainAccessResource.setWhiteRemoteAddress(plainAccessConfig.getWhiteRemoteAddress()); - - plainAccessResource.setAdmin(plainAccessConfig.isAdmin()); - - plainAccessResource.setDefaultGroupPerm(Permission.parsePermFromString(plainAccessConfig.getDefaultGroupPerm())); - plainAccessResource.setDefaultTopicPerm(Permission.parsePermFromString(plainAccessConfig.getDefaultTopicPerm())); - - Permission.parseResourcePerms(plainAccessResource, false, plainAccessConfig.getGroupPerms()); - Permission.parseResourcePerms(plainAccessResource, true, plainAccessConfig.getTopicPerms()); - - plainAccessResource.setRemoteAddressStrategy(remoteAddressStrategyFactory. - getRemoteAddressStrategy(plainAccessResource.getWhiteRemoteAddress())); - - return plainAccessResource; + return PlainAccessResource.build(plainAccessConfig, remoteAddressStrategyFactory. + getRemoteAddressStrategy(plainAccessConfig.getWhiteRemoteAddress())); } public void validate(PlainAccessResource plainAccessResource) { @@ -633,16 +606,19 @@ public void validate(PlainAccessResource plainAccessResource) { } if (plainAccessResource.getAccessKey() == null) { - throw new AclException(String.format("No accessKey is configured")); + throw new AclException("No accessKey is configured"); } if (!accessKeyTable.containsKey(plainAccessResource.getAccessKey())) { throw new AclException(String.format("No acl config for %s", plainAccessResource.getAccessKey())); } - // Check the white addr for accesskey + // Check the white addr for accessKey String aclFileName = accessKeyTable.get(plainAccessResource.getAccessKey()); - PlainAccessResource ownedAccess = aclPlainAccessResourceMap.get(aclFileName).get(plainAccessResource.getAccessKey()); + PlainAccessResource ownedAccess = aclPlainAccessResourceMap.getOrDefault(aclFileName, new HashMap<>()).get(plainAccessResource.getAccessKey()); + if (ownedAccess == null) { + throw new AclException(String.format("No PlainAccessResource for accessKey=%s", plainAccessResource.getAccessKey())); + } if (ownedAccess.getRemoteAddressStrategy().match(plainAccessResource)) { return; } @@ -652,8 +628,17 @@ public void validate(PlainAccessResource plainAccessResource) { if (!signature.equals(plainAccessResource.getSignature())) { throw new AclException(String.format("Check signature failed for accessKey=%s", plainAccessResource.getAccessKey())); } - // Check perm of each resource + //Skip the topic RMQ_SYS_TRACE_TOPIC permission check,if the topic RMQ_SYS_TRACE_TOPIC is used for message trace + Map resourcePermMap = plainAccessResource.getResourcePermMap(); + if (resourcePermMap != null) { + Byte permission = resourcePermMap.get(TopicValidator.RMQ_SYS_TRACE_TOPIC); + if (permission != null && permission == Permission.PUB) { + return; + } + } + + // Check perm of each resource checkPerm(plainAccessResource, ownedAccess); } diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java index 50e6c734ccc..fb4151e5366 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java @@ -23,12 +23,12 @@ import org.apache.rocketmq.acl.common.AclException; import org.apache.rocketmq.acl.common.AclUtils; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class RemoteAddressStrategyFactory { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); public static final NullRemoteAddressStrategy NULL_NET_ADDRESS_STRATEGY = new NullRemoteAddressStrategy(); @@ -50,18 +50,18 @@ public RemoteAddressStrategy getRemoteAddressStrategy(String remoteAddr) { String[] strArray = StringUtils.split(remoteAddr, ":"); String last = strArray[strArray.length - 1]; if (!last.startsWith("{")) { - throw new AclException(String.format("MultipleRemoteAddressStrategy netaddress examine scope Exception netaddress", remoteAddr)); + throw new AclException(String.format("MultipleRemoteAddressStrategy netAddress examine scope Exception netAddress: %s", remoteAddr)); } return new MultipleRemoteAddressStrategy(AclUtils.getAddresses(remoteAddr, last)); } else { String[] strArray = StringUtils.split(remoteAddr, "."); // However a right IP String provided by user,it always can be divided into 4 parts by '.'. if (strArray.length < 4) { - throw new AclException(String.format("MultipleRemoteAddressStrategy has got a/some wrong format IP(s) ", remoteAddr)); + throw new AclException(String.format("MultipleRemoteAddressStrategy has got a/some wrong format IP(s): %s ", remoteAddr)); } String lastStr = strArray[strArray.length - 1]; if (!lastStr.startsWith("{")) { - throw new AclException(String.format("MultipleRemoteAddressStrategy netaddress examine scope Exception netaddress", remoteAddr)); + throw new AclException(String.format("MultipleRemoteAddressStrategy netAddress examine scope Exception netAddress: %s", remoteAddr)); } return new MultipleRemoteAddressStrategy(AclUtils.getAddresses(remoteAddr, lastStr)); } @@ -96,13 +96,13 @@ public static class MultipleRemoteAddressStrategy implements RemoteAddressStrate public MultipleRemoteAddressStrategy(String[] strArray) { InetAddressValidator validator = InetAddressValidator.getInstance(); - for (String netaddress : strArray) { - if (validator.isValidInet4Address(netaddress)) { - multipleSet.add(netaddress); - } else if (validator.isValidInet6Address(netaddress)) { - multipleSet.add(AclUtils.expandIP(netaddress, 8)); + for (String netAddress : strArray) { + if (validator.isValidInet4Address(netAddress)) { + multipleSet.add(netAddress); + } else if (validator.isValidInet6Address(netAddress)) { + multipleSet.add(AclUtils.expandIP(netAddress, 8)); } else { - throw new AclException(String.format("Netaddress examine Exception netaddress is %s", netaddress)); + throw new AclException(String.format("NetAddress examine Exception netAddress is %s", netAddress)); } } } @@ -121,20 +121,22 @@ public boolean match(PlainAccessResource plainAccessResource) { public static class OneRemoteAddressStrategy implements RemoteAddressStrategy { - private String netaddress; + private String netAddress; - public OneRemoteAddressStrategy(String netaddress) { - this.netaddress = netaddress; + public OneRemoteAddressStrategy(String netAddress) { + this.netAddress = netAddress; InetAddressValidator validator = InetAddressValidator.getInstance(); - if (!(validator.isValidInet4Address(netaddress) || validator.isValidInet6Address(netaddress))) { - throw new AclException(String.format("Netaddress examine Exception netaddress is %s", netaddress)); + if (!(validator.isValidInet4Address(netAddress) || validator.isValidInet6Address( + netAddress))) { + throw new AclException(String.format("NetAddress examine Exception netAddress is %s", + netAddress)); } } @Override public boolean match(PlainAccessResource plainAccessResource) { String writeRemoteAddress = AclUtils.expandIP(plainAccessResource.getWhiteRemoteAddress(), 8).toUpperCase(); - return AclUtils.expandIP(netaddress, 8).toUpperCase().equals(writeRemoteAddress); + return AclUtils.expandIP(netAddress, 8).toUpperCase().equals(writeRemoteAddress); } } @@ -183,14 +185,14 @@ private boolean analysis(String[] strArray, int index) { setValue(0, 255); } else if (AclUtils.isMinus(value)) { if (value.indexOf("-") == 0) { - throw new AclException(String.format("RangeRemoteAddressStrategy netaddress examine scope Exception value %s ", value)); + throw new AclException(String.format("RangeRemoteAddressStrategy netAddress examine scope Exception value %s ", value)); } String[] valueArray = StringUtils.split(value, "-"); this.start = Integer.parseInt(valueArray[0]); this.end = Integer.parseInt(valueArray[1]); if (!(AclUtils.isScope(end) && AclUtils.isScope(start) && start <= end)) { - throw new AclException(String.format("RangeRemoteAddressStrategy netaddress examine scope Exception start is %s , end is %s", start, end)); + throw new AclException(String.format("RangeRemoteAddressStrategy netAddress examine scope Exception start is %s , end is %s", start, end)); } } return this.end > 0; @@ -205,16 +207,16 @@ private boolean ipv6Analysis(String[] strArray, int index) { setValue(min, max); } else if (AclUtils.isMinus(value)) { if (value.indexOf("-") == 0) { - throw new AclException(String.format("RangeRemoteAddressStrategy netaddress examine scope Exception value %s ", value)); + throw new AclException(String.format("RangeRemoteAddressStrategy netAddress examine scope Exception value %s ", value)); } String[] valueArray = StringUtils.split(value, "-"); this.start = Integer.parseInt(valueArray[0], 16); this.end = Integer.parseInt(valueArray[1], 16); if (!(AclUtils.isIPv6Scope(end) && AclUtils.isIPv6Scope(start) && start <= end)) { - throw new AclException(String.format("RangeRemoteAddressStrategy netaddress examine scope Exception start is %s , end is %s", start, end)); + throw new AclException(String.format("RangeRemoteAddressStrategy netAddress examine scope Exception start is %s , end is %s", start, end)); } } - return this.end > 0 ? true : false; + return this.end > 0; } private void setValue(int start, int end) { @@ -237,18 +239,14 @@ public boolean match(PlainAccessResource plainAccessResource) { value = netAddress.substring(this.head.length(), netAddress.lastIndexOf('.', netAddress.lastIndexOf('.') - 1)); } Integer address = Integer.valueOf(value); - if (address >= this.start && address <= this.end) { - return true; - } + return address >= this.start && address <= this.end; } } else if (validator.isValidInet6Address(netAddress)) { netAddress = AclUtils.expandIP(netAddress, 8).toUpperCase(); if (netAddress.startsWith(this.head)) { String value = netAddress.substring(5 * index, 5 * index + 4); Integer address = Integer.parseInt(value, 16); - if (address >= this.start && address <= this.end) { - return true; - } + return address >= this.start && address <= this.end; } } return false; diff --git a/acl/src/test/java/org/apache/rocketmq/acl/common/AclClientRPCHookTest.java b/acl/src/test/java/org/apache/rocketmq/acl/common/AclClientRPCHookTest.java new file mode 100644 index 00000000000..9789ed191c4 --- /dev/null +++ b/acl/src/test/java/org/apache/rocketmq/acl/common/AclClientRPCHookTest.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.lang.reflect.Field; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +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 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); + } + } +} diff --git a/acl/src/test/java/org/apache/rocketmq/acl/common/AclSignerTest.java b/acl/src/test/java/org/apache/rocketmq/acl/common/AclSignerTest.java index eec626357fc..2680d6bd820 100644 --- a/acl/src/test/java/org/apache/rocketmq/acl/common/AclSignerTest.java +++ b/acl/src/test/java/org/apache/rocketmq/acl/common/AclSignerTest.java @@ -16,19 +16,21 @@ */ package org.apache.rocketmq.acl.common; +import org.junit.Assert; import org.junit.Test; public class AclSignerTest { @Test(expected = Exception.class) - public void calSignatureExceptionTest(){ + public void calSignatureExceptionTest() { AclSigner.calSignature(new byte[]{},""); } @Test - public void calSignatureTest(){ - AclSigner.calSignature("RocketMQ","12345678"); - AclSigner.calSignature("RocketMQ".getBytes(),"12345678"); + public void calSignatureTest() { + String expectedSignature = "IUc8rrO/0gDch8CjObLQsW2rsiA="; + Assert.assertEquals(expectedSignature, AclSigner.calSignature("RocketMQ", "12345678")); + Assert.assertEquals(expectedSignature, AclSigner.calSignature("RocketMQ".getBytes(), "12345678")); } } diff --git a/acl/src/test/java/org/apache/rocketmq/acl/common/AclUtilsTest.java b/acl/src/test/java/org/apache/rocketmq/acl/common/AclUtilsTest.java index e79cd908c0c..03bceade770 100644 --- a/acl/src/test/java/org/apache/rocketmq/acl/common/AclUtilsTest.java +++ b/acl/src/test/java/org/apache/rocketmq/acl/common/AclUtilsTest.java @@ -17,28 +17,30 @@ package org.apache.rocketmq.acl.common; import com.alibaba.fastjson.JSONObject; +import org.apache.commons.lang3.StringUtils; +import org.apache.rocketmq.acl.plain.PlainAccessData; +import org.apache.rocketmq.common.PlainAccessConfig; +import org.apache.rocketmq.remoting.RPCHook; +import org.junit.Assert; +import org.junit.Test; + import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; -import org.apache.commons.lang3.StringUtils; -import org.apache.rocketmq.remoting.RPCHook; -import org.junit.Assert; -import org.junit.Test; +import java.util.UUID; public class AclUtilsTest { @Test - public void getAddresses() { + 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<>(); - for (String a : addressArray) { - newAddressList.add(a); - } + List newAddressList = new ArrayList<>(Arrays.asList(addressArray)); List addressList = new ArrayList<>(); addressList.add("1.1.1.1"); @@ -47,13 +49,11 @@ public void getAddresses() { addressList.add("1.1.1.4"); Assert.assertEquals(newAddressList, addressList); -// IPv6 test + // 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<>(); - for (String a : ipv6AddressArray) { - newIPv6AddressList.add(a); - } + Collections.addAll(newIPv6AddressList, ipv6AddressArray); List ipv6AddressList = new ArrayList<>(); ipv6AddressList.add("1:ac41:9987::bb22:666:1"); @@ -64,7 +64,7 @@ public void getAddresses() { } @Test - public void isScopeStringArray() { + public void testIsScope_StringArray() { String address = "12"; for (int i = 0; i < 6; i++) { @@ -79,42 +79,41 @@ public void isScopeStringArray() { } @Test - public void isScopeArray() { - String[] adderss = StringUtils.split("12.12.12.12", "."); - boolean isScope = AclUtils.isScope(adderss, 4); + public void testIsScope_Array() { + String[] address = StringUtils.split("12.12.12.12", "."); + boolean isScope = AclUtils.isScope(address, 4); Assert.assertTrue(isScope); - isScope = AclUtils.isScope(adderss, 3); + isScope = AclUtils.isScope(address, 3); Assert.assertTrue(isScope); - adderss = StringUtils.split("12.12.1222.1222", "."); - isScope = AclUtils.isScope(adderss, 4); + address = StringUtils.split("12.12.1222.1222", "."); + isScope = AclUtils.isScope(address, 4); Assert.assertFalse(isScope); - isScope = AclUtils.isScope(adderss, 3); + isScope = AclUtils.isScope(address, 3); Assert.assertFalse(isScope); -// IPv6 test - adderss = StringUtils.split("1050:0000:0000:0000:0005:0600:300c:326b", ":"); - isScope = AclUtils.isIPv6Scope(adderss, 8); + // IPv6 test + address = StringUtils.split("1050:0000:0000:0000:0005:0600:300c:326b", ":"); + isScope = AclUtils.isIPv6Scope(address, 8); Assert.assertTrue(isScope); - isScope = AclUtils.isIPv6Scope(adderss, 4); + isScope = AclUtils.isIPv6Scope(address, 4); Assert.assertTrue(isScope); - adderss = StringUtils.split("1050:9876:0000:0000:0005:akkg:300c:326b", ":"); - isScope = AclUtils.isIPv6Scope(adderss, 8); + address = StringUtils.split("1050:9876:0000:0000:0005:akkg:300c:326b", ":"); + isScope = AclUtils.isIPv6Scope(address, 8); Assert.assertFalse(isScope); - isScope = AclUtils.isIPv6Scope(adderss, 4); + isScope = AclUtils.isIPv6Scope(address, 4); Assert.assertTrue(isScope); - adderss = StringUtils.split(AclUtils.expandIP("1050::0005:akkg:300c:326b", 8), ":"); - isScope = AclUtils.isIPv6Scope(adderss, 8); + address = StringUtils.split(AclUtils.expandIP("1050::0005:akkg:300c:326b", 8), ":"); + isScope = AclUtils.isIPv6Scope(address, 8); Assert.assertFalse(isScope); - isScope = AclUtils.isIPv6Scope(adderss, 4); + isScope = AclUtils.isIPv6Scope(address, 4); Assert.assertTrue(isScope); - } @Test - public void isScopeStringTest() { + public void testIsScope_String() { for (int i = 0; i < 256; i++) { boolean isScope = AclUtils.isScope(i + ""); Assert.assertTrue(isScope); @@ -126,7 +125,7 @@ public void isScopeStringTest() { } @Test - public void isScopeTest() { + public void testIsScope_Integral() { for (int i = 0; i < 256; i++) { boolean isScope = AclUtils.isScope(i); Assert.assertTrue(isScope); @@ -136,7 +135,7 @@ public void isScopeTest() { isScope = AclUtils.isScope(256); Assert.assertFalse(isScope); - // IPv6 test + // IPv6 test int min = Integer.parseInt("0", 16); int max = Integer.parseInt("ffff", 16); for (int i = min; i < max + 1; i++) { @@ -147,11 +146,10 @@ public void isScopeTest() { Assert.assertFalse(isScope); isScope = AclUtils.isIPv6Scope(max + 1); Assert.assertFalse(isScope); - } @Test - public void isAsteriskTest() { + public void testIsAsterisk() { boolean isAsterisk = AclUtils.isAsterisk("*"); Assert.assertTrue(isAsterisk); @@ -160,7 +158,7 @@ public void isAsteriskTest() { } @Test - public void isColonTest() { + public void testIsComma() { boolean isColon = AclUtils.isComma(","); Assert.assertTrue(isColon); @@ -169,7 +167,7 @@ public void isColonTest() { } @Test - public void isMinusTest() { + public void testIsMinus() { boolean isMinus = AclUtils.isMinus("-"); Assert.assertTrue(isMinus); @@ -178,30 +176,21 @@ public void isMinusTest() { } @Test - public void v6ipProcessTest() { + public void testV6ipProcess() { String remoteAddr = "5::7:6:1-200:*"; - String[] strArray = StringUtils.split(remoteAddr, ":"); Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr), "0005:0000:0000:0000:0007:0006"); -// Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr, strArray, 3), "0005:0000:0000:0000:0007:0006"); remoteAddr = "5::7:6:1-200"; - strArray = StringUtils.split(remoteAddr, ":"); Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr), "0005:0000:0000:0000:0000:0007:0006"); -// Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr, strArray, 3), "0005:0000:0000:0000:0000:0007:0006"); - remoteAddr = "5::7:6:*"; - strArray = StringUtils.split(remoteAddr, ":"); Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr), "0005:0000:0000:0000:0000:0007:0006"); -// Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr, strArray, 3), "0005:0000:0000:0000:0000:0007:0006"); remoteAddr = "5:7:6:*"; - strArray = StringUtils.split(remoteAddr, ":"); Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr), "0005:0007:0006"); -// Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr, strArray, 3), "0005:0007:0006"); } @Test - public void expandIPTest() { + 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"); @@ -214,101 +203,97 @@ public void expandIPTest() { @SuppressWarnings("unchecked") @Test - public void getYamlDataObjectTest() { + public void testGetYamlDataObject() throws IOException { + try (InputStream is = AclUtilsTest.class.getClassLoader().getResourceAsStream("conf/plain_acl_correct.yml")) { + Map map = AclUtils.getYamlDataObject(is, Map.class); + Assert.assertNotNull(map); + Assert.assertFalse(map.isEmpty()); + } + } - Map map = AclUtils.getYamlDataObject("src/test/resources/conf/plain_acl_correct.yml", Map.class); - Assert.assertFalse(map.isEmpty()); + 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 writeDataObject2YamlFileTest() throws IOException { - - String targetFileName = "src/test/resources/conf/plain_write_acl.yml"; + String targetFileName = randomTmpFile(); File transport = new File(targetFileName); - transport.delete(); - transport.createNewFile(); + Assert.assertTrue(transport.createNewFile()); + transport.deleteOnExit(); - Map aclYamlMap = new HashMap(); + PlainAccessData aclYamlMap = new PlainAccessData(); // For globalWhiteRemoteAddrs element in acl yaml config file - List globalWhiteRemoteAddrs = new ArrayList(); + List globalWhiteRemoteAddrs = new ArrayList<>(); globalWhiteRemoteAddrs.add("10.10.103.*"); globalWhiteRemoteAddrs.add("192.168.0.*"); - aclYamlMap.put("globalWhiteRemoteAddrs", globalWhiteRemoteAddrs); + aclYamlMap.setGlobalWhiteRemoteAddresses(globalWhiteRemoteAddrs); // For accounts element in acl yaml config file - List> accounts = new ArrayList>(); - Map accountsMap = new LinkedHashMap() { + List accounts = new ArrayList<>(); + PlainAccessConfig accountsMap = new PlainAccessConfig() { { - put("accessKey", "RocketMQ"); - put("secretKey", "12345678"); - put("whiteRemoteAddress", "whiteRemoteAddress"); - put("admin", "true"); + setAccessKey("RocketMQ"); + setSecretKey("12345678"); + setWhiteRemoteAddress("whiteRemoteAddress"); + setAdmin(true); } }; accounts.add(accountsMap); - aclYamlMap.put("accounts", accounts); + aclYamlMap.setAccounts(accounts); Assert.assertTrue(AclUtils.writeDataObject(targetFileName, aclYamlMap)); - - transport.delete(); } @Test public void updateExistedYamlFileTest() throws IOException { - - String targetFileName = "src/test/resources/conf/plain_update_acl.yml"; + String targetFileName = randomTmpFile(); File transport = new File(targetFileName); - transport.delete(); - transport.createNewFile(); + Assert.assertTrue(transport.createNewFile()); + transport.deleteOnExit(); - Map aclYamlMap = new HashMap(); + PlainAccessData aclYamlMap = new PlainAccessData(); // For globalWhiteRemoteAddrs element in acl yaml config file - List globalWhiteRemoteAddrs = new ArrayList(); + List globalWhiteRemoteAddrs = new ArrayList<>(); globalWhiteRemoteAddrs.add("10.10.103.*"); globalWhiteRemoteAddrs.add("192.168.0.*"); - aclYamlMap.put("globalWhiteRemoteAddrs", globalWhiteRemoteAddrs); + aclYamlMap.setGlobalWhiteRemoteAddresses(globalWhiteRemoteAddrs); // Write file to yaml file AclUtils.writeDataObject(targetFileName, aclYamlMap); - Map updatedMap = AclUtils.getYamlDataObject(targetFileName, Map.class); - List globalWhiteRemoteAddrList = (List) updatedMap.get("globalWhiteRemoteAddrs"); + PlainAccessData updatedMap = AclUtils.getYamlDataObject(targetFileName, PlainAccessData.class); + List globalWhiteRemoteAddrList = updatedMap.getGlobalWhiteRemoteAddresses(); globalWhiteRemoteAddrList.clear(); globalWhiteRemoteAddrList.add("192.168.1.2"); // Update file and flush to yaml file AclUtils.writeDataObject(targetFileName, updatedMap); - Map readableMap = AclUtils.getYamlDataObject(targetFileName, Map.class); - List updatedGlobalWhiteRemoteAddrs = (List) readableMap.get("globalWhiteRemoteAddrs"); + PlainAccessData readableMap = AclUtils.getYamlDataObject(targetFileName, PlainAccessData.class); + List updatedGlobalWhiteRemoteAddrs = readableMap.getGlobalWhiteRemoteAddresses(); Assert.assertEquals("192.168.1.2", updatedGlobalWhiteRemoteAddrs.get(0)); - - transport.delete(); } @Test public void getYamlDataIgnoreFileNotFoundExceptionTest() { JSONObject yamlDataObject = AclUtils.getYamlDataObject("plain_acl.yml", JSONObject.class); - Assert.assertTrue(yamlDataObject == null); + Assert.assertNull(yamlDataObject); } - @Test - public void getAclRPCHookTest() { - - //RPCHook errorContRPCHook = AclUtils.getAclRPCHook("src/test/resources/conf/plain_acl_format_error.yml"); - //Assert.assertNull(errorContRPCHook); - - RPCHook noFileRPCHook = AclUtils.getAclRPCHook("src/test/resources/plain_acl_format_error1.yml"); - Assert.assertNull(noFileRPCHook); - - //RPCHook emptyContRPCHook = AclUtils.getAclRPCHook("src/test/resources/conf/plain_acl_null.yml"); - //Assert.assertNull(emptyContRPCHook); - - RPCHook incompleteContRPCHook = AclUtils.getAclRPCHook("src/test/resources/conf/plain_acl_incomplete.yml"); - Assert.assertNull(incompleteContRPCHook); + 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); + } } - } diff --git a/acl/src/test/java/org/apache/rocketmq/acl/common/PermissionTest.java b/acl/src/test/java/org/apache/rocketmq/acl/common/PermissionTest.java index c824065f7ff..8fd8052c8a4 100644 --- a/acl/src/test/java/org/apache/rocketmq/acl/common/PermissionTest.java +++ b/acl/src/test/java/org/apache/rocketmq/acl/common/PermissionTest.java @@ -37,10 +37,10 @@ public void fromStringGetPermissionTest() { Assert.assertEquals(perm, Permission.SUB); perm = Permission.parsePermFromString("PUB|SUB"); - Assert.assertEquals(perm, Permission.PUB|Permission.SUB); + Assert.assertEquals(perm, Permission.PUB | Permission.SUB); perm = Permission.parsePermFromString("SUB|PUB"); - Assert.assertEquals(perm, Permission.PUB|Permission.SUB); + Assert.assertEquals(perm, Permission.PUB | Permission.SUB); perm = Permission.parsePermFromString("DENY"); Assert.assertEquals(perm, Permission.DENY); @@ -64,13 +64,13 @@ public void checkPermissionTest() { boo = Permission.checkPermission(Permission.SUB, Permission.SUB); Assert.assertTrue(boo); - boo = Permission.checkPermission(Permission.PUB, (byte) (Permission.PUB|Permission.SUB)); + boo = Permission.checkPermission(Permission.PUB, (byte) (Permission.PUB | Permission.SUB)); Assert.assertTrue(boo); - boo = Permission.checkPermission(Permission.SUB, (byte) (Permission.PUB|Permission.SUB)); + boo = Permission.checkPermission(Permission.SUB, (byte) (Permission.PUB | Permission.SUB)); Assert.assertTrue(boo); - boo = Permission.checkPermission(Permission.ANY, (byte) (Permission.PUB|Permission.SUB)); + boo = Permission.checkPermission(Permission.ANY, (byte) (Permission.PUB | Permission.SUB)); Assert.assertTrue(boo); boo = Permission.checkPermission(Permission.ANY, Permission.SUB); @@ -112,7 +112,7 @@ public void setTopicPermTest() { Assert.assertEquals(perm, Permission.DENY); perm = resourcePermMap.get(PlainAccessResource.getRetryTopic("groupB")); - Assert.assertEquals(perm,Permission.PUB|Permission.SUB); + Assert.assertEquals(perm,Permission.PUB | Permission.SUB); perm = resourcePermMap.get(PlainAccessResource.getRetryTopic("groupC")); Assert.assertEquals(perm, Permission.PUB); @@ -128,7 +128,7 @@ public void setTopicPermTest() { Assert.assertEquals(perm, Permission.DENY); perm = resourcePermMap.get("topicB"); - Assert.assertEquals(perm, Permission.PUB|Permission.SUB); + Assert.assertEquals(perm, Permission.PUB | Permission.SUB); perm = resourcePermMap.get("topicC"); Assert.assertEquals(perm, Permission.PUB); @@ -156,15 +156,15 @@ public void checkAdminCodeTest() { } @Test - public void AclExceptionTest(){ + 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"); + aclException.setStatus("netAddress examine scope Exception netAddress"); + Assert.assertEquals(aclException.getStatus(),"netAddress examine scope Exception netAddress"); } @Test diff --git a/acl/src/test/java/org/apache/rocketmq/acl/common/SessionCredentialsTest.java b/acl/src/test/java/org/apache/rocketmq/acl/common/SessionCredentialsTest.java index a1a4bde4f87..79512f14790 100644 --- a/acl/src/test/java/org/apache/rocketmq/acl/common/SessionCredentialsTest.java +++ b/acl/src/test/java/org/apache/rocketmq/acl/common/SessionCredentialsTest.java @@ -24,17 +24,17 @@ public class SessionCredentialsTest { @Test - public void equalsTest(){ - SessionCredentials sessionCredentials=new SessionCredentials("RocketMQ","12345678"); + public void equalsTest() { + SessionCredentials sessionCredentials = new SessionCredentials("RocketMQ","12345678"); sessionCredentials.setSecurityToken("abcd"); - SessionCredentials other=new SessionCredentials("RocketMQ","12345678","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(); + 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"); @@ -42,9 +42,9 @@ public void updateContentTest(){ } @Test - public void SessionCredentialHashCodeTest(){ - SessionCredentials sessionCredentials=new SessionCredentials(); - Properties properties=new Properties(); + 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"); @@ -53,16 +53,16 @@ public void SessionCredentialHashCodeTest(){ } @Test - public void SessionCredentialEqualsTest(){ - SessionCredentials sessionCredential1 =new SessionCredentials(); - Properties properties1=new Properties(); + 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(); + 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"); @@ -75,9 +75,9 @@ public void SessionCredentialEqualsTest(){ } @Test - public void SessionCredentialToStringTest(){ - SessionCredentials sessionCredential1 =new SessionCredentials(); - Properties properties1=new Properties(); + 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"); diff --git a/acl/src/test/java/org/apache/rocketmq/acl/plain/AclTestHelper.java b/acl/src/test/java/org/apache/rocketmq/acl/plain/AclTestHelper.java new file mode 100644 index 00000000000..a1bfc53ed51 --- /dev/null +++ b/acl/src/test/java/org/apache/rocketmq/acl/plain/AclTestHelper.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.plain; + +import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.UUID; +import java.util.Iterator; +import org.junit.Assert; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +public final class AclTestHelper { + private AclTestHelper() { + } + + private static void copyTo(String path, InputStream src, File dstDir, String flag, boolean into) + 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; + if (into) { + dir = new File(dir, flag); + if (!dir.exists()) { + Assert.assertTrue(dir.mkdirs()); + } + } + 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); + } + } + } + + public static void recursiveDelete(File file) { + if (file.isFile()) { + file.delete(); + } else { + File[] files = file.listFiles(); + if (null != files) { + for (File f : files) { + recursiveDelete(f); + } + } + file.delete(); + } + } + + public static File copyResources(String folder) throws IOException { + return copyResources(folder, false); + } + + public static File copyResources(String folder, boolean into) throws IOException { + File home = new File(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString().replace('-', '_')); + if (!home.exists()) { + Assert.assertTrue(home.mkdirs()); + } + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(AclTestHelper.class.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, home, folder, into); + } + } + return home; + } +} diff --git a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessControlFlowTest.java b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessControlFlowTest.java new file mode 100644 index 00000000000..5193457146d --- /dev/null +++ b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessControlFlowTest.java @@ -0,0 +1,311 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.plain; + +import org.apache.rocketmq.acl.common.AclClientRPCHook; +import org.apache.rocketmq.acl.common.AclConstants; +import org.apache.rocketmq.acl.common.AclException; +import org.apache.rocketmq.acl.common.AclUtils; +import org.apache.rocketmq.acl.common.SessionCredentials; +import org.apache.rocketmq.common.AclConfig; +import org.apache.rocketmq.common.PlainAccessConfig; +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.PullMessageRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2; +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + *

In this class, we'll test the following scenarios, each containing several consecutive operations on ACL, + *

like updating and deleting ACL, changing config files and checking validations. + *

Case 1: Only conf/plain_acl.yml exists; + *

Case 2: Only conf/acl/plain_acl.yml exists; + *

Case 3: Both conf/plain_acl.yml and conf/acl/plain_acl.yml exists. + */ +public class PlainAccessControlFlowTest { + public static final String DEFAULT_TOPIC = "topic-acl"; + + public static final String DEFAULT_GROUP = "GID_acl"; + + public static final String DEFAULT_PRODUCER_AK = "ak11111"; + public static final String DEFAULT_PRODUCER_SK = "1234567"; + + public static final String DEFAULT_CONSUMER_SK = "7654321"; + public static final String DEFAULT_CONSUMER_AK = "ak22222"; + + public static final String DEFAULT_GLOBAL_WHITE_ADDR = "172.16.123.123"; + public static final List DEFAULT_GLOBAL_WHITE_ADDRS_LIST = Collections.singletonList(DEFAULT_GLOBAL_WHITE_ADDR); + + @Test + public void testEmptyAclFolderCase() throws NoSuchFieldException, IllegalAccessException, + IOException { + String folder = "empty_acl_folder_conf"; + File home = AclTestHelper.copyResources(folder); + System.setProperty("rocketmq.home.dir", home.getAbsolutePath()); + PlainAccessValidator plainAccessValidator = new PlainAccessValidator(); + checkDefaultAclFileExists(); + testValidationAfterConsecutiveUpdates(plainAccessValidator); + testValidationAfterConfigFileChanged(plainAccessValidator); + AclTestHelper.recursiveDelete(home); + } + + @Test + public void testOnlyAclFolderCase() throws NoSuchFieldException, IllegalAccessException, IOException { + String folder = "only_acl_folder_conf"; + File home = AclTestHelper.copyResources(folder); + System.setProperty("rocketmq.home.dir", home.getAbsolutePath()); + PlainAccessValidator plainAccessValidator = new PlainAccessValidator(); + checkDefaultAclFileExists(); + testValidationAfterConsecutiveUpdates(plainAccessValidator); + testValidationAfterConfigFileChanged(plainAccessValidator); + AclTestHelper.recursiveDelete(home); + } + + @Test + public void testBothAclFileAndFolderCase() throws NoSuchFieldException, IllegalAccessException, + IOException { + String folder = "both_acl_file_folder_conf"; + File root = AclTestHelper.copyResources(folder); + System.setProperty("rocketmq.home.dir", root.getAbsolutePath()); + PlainAccessValidator plainAccessValidator = new PlainAccessValidator(); + checkDefaultAclFileExists(); + testValidationAfterConsecutiveUpdates(plainAccessValidator); + testValidationAfterConfigFileChanged(plainAccessValidator); + AclTestHelper.recursiveDelete(root); + } + + private void testValidationAfterConfigFileChanged( + PlainAccessValidator plainAccessValidator) throws NoSuchFieldException, IllegalAccessException { + PlainAccessConfig producerAccessConfig = generateProducerAccessConfig(); + PlainAccessConfig consumerAccessConfig = generateConsumerAccessConfig(); + List plainAccessConfigList = new LinkedList<>(); + plainAccessConfigList.add(producerAccessConfig); + plainAccessConfigList.add(consumerAccessConfig); + PlainAccessData ymlMap = new PlainAccessData(); + ymlMap.setAccounts(plainAccessConfigList); + + // write prepared PlainAccessConfigs to file + final String aclConfigFile = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml"; + AclUtils.writeDataObject(aclConfigFile, ymlMap); + + loadConfigFile(plainAccessValidator, aclConfigFile); + + // check if added successfully + final AclConfig allAclConfig = plainAccessValidator.getAllAclConfig(); + final List plainAccessConfigs = allAclConfig.getPlainAccessConfigs(); + checkPlainAccessConfig(producerAccessConfig, plainAccessConfigs); + checkPlainAccessConfig(consumerAccessConfig, plainAccessConfigs); + + //delete consumer account + plainAccessConfigList.remove(consumerAccessConfig); + AclUtils.writeDataObject(aclConfigFile, ymlMap); + + loadConfigFile(plainAccessValidator, aclConfigFile); + + // sending messages will be successful using prepared credentials + SessionCredentials producerCredential = new SessionCredentials(DEFAULT_PRODUCER_AK, DEFAULT_PRODUCER_SK); + AclClientRPCHook producerHook = new AclClientRPCHook(producerCredential); + validateSendMessage(RequestCode.SEND_MESSAGE, DEFAULT_TOPIC, producerHook, "", plainAccessValidator); + validateSendMessage(RequestCode.SEND_MESSAGE_V2, DEFAULT_TOPIC, producerHook, "", plainAccessValidator); + + // consuming messages will be failed for account has been deleted + SessionCredentials consumerCredential = new SessionCredentials(DEFAULT_CONSUMER_AK, DEFAULT_CONSUMER_SK); + AclClientRPCHook consumerHook = new AclClientRPCHook(consumerCredential); + boolean isConsumeFailed = false; + try { + validatePullMessage(DEFAULT_TOPIC, DEFAULT_GROUP, consumerHook, "", plainAccessValidator); + } catch (AclException e) { + isConsumeFailed = true; + } + Assert.assertTrue("Message should not be consumed after account deleted", isConsumeFailed); + + } + + private void testValidationAfterConsecutiveUpdates( + PlainAccessValidator plainAccessValidator) throws NoSuchFieldException, IllegalAccessException { + PlainAccessConfig producerAccessConfig = generateProducerAccessConfig(); + plainAccessValidator.updateAccessConfig(producerAccessConfig); + + PlainAccessConfig consumerAccessConfig = generateConsumerAccessConfig(); + plainAccessValidator.updateAccessConfig(consumerAccessConfig); + + plainAccessValidator.updateGlobalWhiteAddrsConfig(DEFAULT_GLOBAL_WHITE_ADDRS_LIST, null); + + // check if the above config updated successfully + final AclConfig allAclConfig = plainAccessValidator.getAllAclConfig(); + final List plainAccessConfigs = allAclConfig.getPlainAccessConfigs(); + checkPlainAccessConfig(producerAccessConfig, plainAccessConfigs); + checkPlainAccessConfig(consumerAccessConfig, plainAccessConfigs); + + Assert.assertEquals(DEFAULT_GLOBAL_WHITE_ADDRS_LIST, allAclConfig.getGlobalWhiteAddrs()); + + // check sending and consuming messages + SessionCredentials producerCredential = new SessionCredentials(DEFAULT_PRODUCER_AK, DEFAULT_PRODUCER_SK); + AclClientRPCHook producerHook = new AclClientRPCHook(producerCredential); + validateSendMessage(RequestCode.SEND_MESSAGE, DEFAULT_TOPIC, producerHook, "", plainAccessValidator); + validateSendMessage(RequestCode.SEND_MESSAGE_V2, DEFAULT_TOPIC, producerHook, "", plainAccessValidator); + + SessionCredentials consumerCredential = new SessionCredentials(DEFAULT_CONSUMER_AK, DEFAULT_CONSUMER_SK); + AclClientRPCHook consumerHook = new AclClientRPCHook(consumerCredential); + validatePullMessage(DEFAULT_TOPIC, DEFAULT_GROUP, consumerHook, "", plainAccessValidator); + + // load from file + loadConfigFile(plainAccessValidator, + System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml"); + SessionCredentials unmatchedCredential = new SessionCredentials("non_exists_sk", "non_exists_sk"); + AclClientRPCHook dummyHook = new AclClientRPCHook(unmatchedCredential); + validateSendMessage(RequestCode.SEND_MESSAGE, DEFAULT_TOPIC, dummyHook, DEFAULT_GLOBAL_WHITE_ADDR, plainAccessValidator); + validateSendMessage(RequestCode.SEND_MESSAGE_V2, DEFAULT_TOPIC, dummyHook, DEFAULT_GLOBAL_WHITE_ADDR, plainAccessValidator); + validatePullMessage(DEFAULT_TOPIC, DEFAULT_GROUP, dummyHook, DEFAULT_GLOBAL_WHITE_ADDR, plainAccessValidator); + + //recheck after reloading + validateSendMessage(RequestCode.SEND_MESSAGE, DEFAULT_TOPIC, producerHook, "", plainAccessValidator); + validateSendMessage(RequestCode.SEND_MESSAGE_V2, DEFAULT_TOPIC, producerHook, "", plainAccessValidator); + validatePullMessage(DEFAULT_TOPIC, DEFAULT_GROUP, consumerHook, "", plainAccessValidator); + + } + + private void loadConfigFile(PlainAccessValidator plainAccessValidator, + String configFileName) throws NoSuchFieldException, IllegalAccessException { + Class clazz = PlainAccessValidator.class; + Field f = clazz.getDeclaredField("aclPlugEngine"); + f.setAccessible(true); + PlainPermissionManager aclPlugEngine = (PlainPermissionManager) f.get(plainAccessValidator); + aclPlugEngine.load(configFileName); + } + + private PlainAccessConfig generateConsumerAccessConfig() { + PlainAccessConfig plainAccessConfig2 = new PlainAccessConfig(); + plainAccessConfig2.setAccessKey(DEFAULT_CONSUMER_AK); + plainAccessConfig2.setSecretKey(DEFAULT_CONSUMER_SK); + plainAccessConfig2.setAdmin(false); + plainAccessConfig2.setDefaultTopicPerm(AclConstants.DENY); + plainAccessConfig2.setDefaultGroupPerm(AclConstants.DENY); + plainAccessConfig2.setTopicPerms(Collections.singletonList(DEFAULT_TOPIC + "=" + AclConstants.SUB)); + plainAccessConfig2.setGroupPerms(Collections.singletonList(DEFAULT_GROUP + "=" + AclConstants.SUB)); + return plainAccessConfig2; + } + + private PlainAccessConfig generateProducerAccessConfig() { + PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); + plainAccessConfig.setAccessKey(DEFAULT_PRODUCER_AK); + plainAccessConfig.setSecretKey(DEFAULT_PRODUCER_SK); + plainAccessConfig.setAdmin(false); + plainAccessConfig.setDefaultTopicPerm(AclConstants.DENY); + plainAccessConfig.setDefaultGroupPerm(AclConstants.DENY); + plainAccessConfig.setTopicPerms(Collections.singletonList(DEFAULT_TOPIC + "=" + AclConstants.PUB)); + return plainAccessConfig; + } + + public void validatePullMessage(String topic, + String group, + AclClientRPCHook aclClientRPCHook, + String remoteAddr, + PlainAccessValidator plainAccessValidator) { + PullMessageRequestHeader pullMessageRequestHeader = new PullMessageRequestHeader(); + pullMessageRequestHeader.setTopic(topic); + pullMessageRequestHeader.setConsumerGroup(group); + RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, + pullMessageRequestHeader); + aclClientRPCHook.doBeforeRequest(remoteAddr, remotingCommand); + ByteBuffer buf = remotingCommand.encodeHeader(); + buf.getInt(); + buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); + buf.position(0); + try { + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse( + RemotingCommand.decode(buf), remoteAddr); + plainAccessValidator.validate(accessResource); + } catch (RemotingCommandException e) { + e.printStackTrace(); + Assert.fail("Should not throw RemotingCommandException"); + } + } + + public void validateSendMessage(int requestCode, + String topic, + AclClientRPCHook aclClientRPCHook, + String remoteAddr, + PlainAccessValidator plainAccessValidator) { + SendMessageRequestHeader messageRequestHeader = new SendMessageRequestHeader(); + messageRequestHeader.setTopic(topic); + RemotingCommand remotingCommand; + if (RequestCode.SEND_MESSAGE == requestCode) { + remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, messageRequestHeader); + } else { + remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, + SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(messageRequestHeader)); + } + + aclClientRPCHook.doBeforeRequest(remoteAddr, remotingCommand); + + ByteBuffer buf = remotingCommand.encodeHeader(); + buf.getInt(); + buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); + buf.position(0); + try { + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse( + RemotingCommand.decode(buf), remoteAddr); + plainAccessValidator.validate(accessResource); + } catch (RemotingCommandException e) { + e.printStackTrace(); + Assert.fail("Should not throw RemotingCommandException"); + } + } + + private void checkPlainAccessConfig(final PlainAccessConfig plainAccessConfig, + final List plainAccessConfigs) { + for (PlainAccessConfig config : plainAccessConfigs) { + if (config.getAccessKey().equals(plainAccessConfig.getAccessKey())) { + Assert.assertEquals(plainAccessConfig.getSecretKey(), config.getSecretKey()); + Assert.assertEquals(plainAccessConfig.isAdmin(), config.isAdmin()); + Assert.assertEquals(plainAccessConfig.getDefaultGroupPerm(), config.getDefaultGroupPerm()); + Assert.assertEquals(plainAccessConfig.getDefaultGroupPerm(), config.getDefaultGroupPerm()); + Assert.assertEquals(plainAccessConfig.getWhiteRemoteAddress(), config.getWhiteRemoteAddress()); + if (null != plainAccessConfig.getTopicPerms()) { + Assert.assertNotNull(config.getTopicPerms()); + Assert.assertTrue(config.getTopicPerms().containsAll(plainAccessConfig.getTopicPerms())); + } + if (null != plainAccessConfig.getGroupPerms()) { + Assert.assertNotNull(config.getGroupPerms()); + Assert.assertTrue(config.getGroupPerms().containsAll(plainAccessConfig.getGroupPerms())); + } + } + } + } + + private void checkDefaultAclFileExists() { + boolean isExists = Files.exists(Paths.get(System.getProperty("rocketmq.home.dir") + + File.separator + "conf" + File.separator + "plain_acl.yml")); + Assert.assertTrue("default acl config file should exist", isExists); + } + +} diff --git a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java index 62d98572053..ef0cffbdcc8 100644 --- a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java +++ b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java @@ -16,9 +16,11 @@ */ package org.apache.rocketmq.acl.plain; +import com.google.common.base.Joiner; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; @@ -33,24 +35,25 @@ import org.apache.rocketmq.acl.common.AclUtils; import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.common.AclConfig; -import org.apache.rocketmq.common.DataVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.PlainAccessConfig; -import org.apache.rocketmq.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupRequestHeader; -import org.apache.rocketmq.common.protocol.header.PullMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.QueryMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeaderV2; -import org.apache.rocketmq.common.protocol.header.UnregisterClientRequestHeader; -import org.apache.rocketmq.common.protocol.header.UpdateConsumerOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData; -import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData; -import org.apache.rocketmq.common.protocol.heartbeat.ProducerData; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.exception.RemotingCommandException; +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.header.ConsumerSendMsgBackRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader; +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.ProducerData; +import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -61,11 +64,13 @@ public class PlainAccessValidatorTest { private AclClientRPCHook aclClient; private SessionCredentials sessionCredentials; + private File confHome; @Before - public void init() { - File file = new File("src/test/resources"); - System.setProperty("rocketmq.home.dir", file.getAbsolutePath()); + public void init() throws IOException { + String folder = "conf"; + confHome = AclTestHelper.copyResources(folder, true); + System.setProperty("rocketmq.home.dir", confHome.getAbsolutePath()); plainAccessValidator = new PlainAccessValidator(); sessionCredentials = new SessionCredentials(); sessionCredentials.setAccessKey("RocketMQ"); @@ -74,6 +79,11 @@ public void init() { aclClient = new AclClientRPCHook(sessionCredentials); } + @After + public void cleanUp() { + AclTestHelper.recursiveDelete(confHome); + } + @Test public void contentTest() { SendMessageRequestHeader messageRequestHeader = new SendMessageRequestHeader(); @@ -110,7 +120,7 @@ public void validateTest() { buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); buf.position(0); try { - PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1"); + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "123.4.5.6"); plainAccessValidator.validate(accessResource); } catch (RemotingCommandException e) { e.printStackTrace(); @@ -132,7 +142,28 @@ public void validateSendMessageTest() { buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); buf.position(0); try { - PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1"); + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "123.4.5.6"); + plainAccessValidator.validate(accessResource); + } catch (RemotingCommandException e) { + e.printStackTrace(); + + Assert.fail("Should not throw IOException"); + } + } + + @Test + public void validateSendMessageToRetryTopicTest() { + SendMessageRequestHeader messageRequestHeader = new SendMessageRequestHeader(); + messageRequestHeader.setTopic(MixAll.getRetryTopic("groupB")); + RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, messageRequestHeader); + aclClient.doBeforeRequest("", remotingCommand); + + ByteBuffer buf = remotingCommand.encodeHeader(); + buf.getInt(); + buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); + buf.position(0); + try { + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "123.4.5.6"); plainAccessValidator.validate(accessResource); } catch (RemotingCommandException e) { e.printStackTrace(); @@ -144,7 +175,7 @@ public void validateSendMessageTest() { @Test public void validateSendMessageV2Test() { SendMessageRequestHeader messageRequestHeader = new SendMessageRequestHeader(); - messageRequestHeader.setTopic("topicC"); + messageRequestHeader.setTopic("topicB"); RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(messageRequestHeader)); aclClient.doBeforeRequest("", remotingCommand); @@ -153,7 +184,28 @@ public void validateSendMessageV2Test() { buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); buf.position(0); try { - PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876"); + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "123.4.5.6"); + plainAccessValidator.validate(accessResource); + } catch (RemotingCommandException e) { + e.printStackTrace(); + + Assert.fail("Should not throw IOException"); + } + } + + @Test + public void validateSendMessageV2ToRetryTopicTest() { + SendMessageRequestHeader messageRequestHeader = new SendMessageRequestHeader(); + messageRequestHeader.setTopic(MixAll.getRetryTopic("groupC")); + RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(messageRequestHeader)); + aclClient.doBeforeRequest("", remotingCommand); + + ByteBuffer buf = remotingCommand.encodeHeader(); + buf.getInt(); + buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); + buf.position(0); + try { + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "123.4.5.6:9876"); plainAccessValidator.validate(accessResource); } catch (RemotingCommandException e) { e.printStackTrace(); @@ -182,7 +234,7 @@ public void validateForAdminCommandWithOutAclRPCHook() { public void validatePullMessageTest() { PullMessageRequestHeader pullMessageRequestHeader = new PullMessageRequestHeader(); pullMessageRequestHeader.setTopic("topicC"); - pullMessageRequestHeader.setConsumerGroup("consumerGroupA"); + pullMessageRequestHeader.setConsumerGroup("groupC"); RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, pullMessageRequestHeader); aclClient.doBeforeRequest("", remotingCommand); ByteBuffer buf = remotingCommand.encodeHeader(); @@ -190,7 +242,7 @@ public void validatePullMessageTest() { buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); buf.position(0); try { - PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876"); + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "123.4.5.6"); plainAccessValidator.validate(accessResource); } catch (RemotingCommandException e) { e.printStackTrace(); @@ -203,7 +255,7 @@ public void validatePullMessageTest() { public void validateConsumeMessageBackTest() { ConsumerSendMsgBackRequestHeader consumerSendMsgBackRequestHeader = new ConsumerSendMsgBackRequestHeader(); consumerSendMsgBackRequestHeader.setOriginTopic("topicC"); - consumerSendMsgBackRequestHeader.setGroup("consumerGroupA"); + consumerSendMsgBackRequestHeader.setGroup("groupC"); RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, consumerSendMsgBackRequestHeader); aclClient.doBeforeRequest("", remotingCommand); ByteBuffer buf = remotingCommand.encodeHeader(); @@ -211,7 +263,7 @@ public void validateConsumeMessageBackTest() { buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); buf.position(0); try { - PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876"); + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "123.4.5.6"); plainAccessValidator.validate(accessResource); } catch (RemotingCommandException e) { e.printStackTrace(); @@ -231,7 +283,7 @@ public void validateQueryMessageTest() { buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); buf.position(0); try { - PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876"); + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "123.4.5.6"); plainAccessValidator.validate(accessResource); } catch (RemotingCommandException e) { e.printStackTrace(); @@ -268,9 +320,9 @@ public void validateHeartBeatTest() { Set consumerDataSet = new HashSet<>(); Set subscriptionDataSet = new HashSet<>(); ProducerData producerData = new ProducerData(); - producerData.setGroupName("producerGroupA"); + producerData.setGroupName("groupB"); ConsumerData consumerData = new ConsumerData(); - consumerData.setGroupName("consumerGroupA"); + consumerData.setGroupName("groupC"); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTopic("topicC"); producerDataSet.add(producerData); @@ -287,7 +339,7 @@ public void validateHeartBeatTest() { buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); buf.position(0); try { - PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876"); + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "123.4.5.6"); plainAccessValidator.validate(accessResource); } catch (RemotingCommandException e) { e.printStackTrace(); @@ -299,7 +351,7 @@ public void validateHeartBeatTest() { @Test public void validateUnRegisterClientTest() { UnregisterClientRequestHeader unregisterClientRequestHeader = new UnregisterClientRequestHeader(); - unregisterClientRequestHeader.setConsumerGroup("consumerGroupA"); + unregisterClientRequestHeader.setConsumerGroup("groupB"); RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.UNREGISTER_CLIENT, unregisterClientRequestHeader); aclClient.doBeforeRequest("", remotingCommand); ByteBuffer buf = remotingCommand.encodeHeader(); @@ -307,7 +359,7 @@ public void validateUnRegisterClientTest() { buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); buf.position(0); try { - PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876"); + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "123.4.5.6"); plainAccessValidator.validate(accessResource); } catch (RemotingCommandException e) { e.printStackTrace(); @@ -319,7 +371,7 @@ public void validateUnRegisterClientTest() { @Test public void validateGetConsumerListByGroupTest() { GetConsumerListByGroupRequestHeader getConsumerListByGroupRequestHeader = new GetConsumerListByGroupRequestHeader(); - getConsumerListByGroupRequestHeader.setConsumerGroup("consumerGroupA"); + getConsumerListByGroupRequestHeader.setConsumerGroup("groupB"); RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_LIST_BY_GROUP, getConsumerListByGroupRequestHeader); aclClient.doBeforeRequest("", remotingCommand); ByteBuffer buf = remotingCommand.encodeHeader(); @@ -327,7 +379,7 @@ public void validateGetConsumerListByGroupTest() { buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); buf.position(0); try { - PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876"); + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "123.4.5.6"); plainAccessValidator.validate(accessResource); } catch (RemotingCommandException e) { e.printStackTrace(); @@ -339,7 +391,7 @@ public void validateGetConsumerListByGroupTest() { @Test public void validateUpdateConsumerOffSetTest() { UpdateConsumerOffsetRequestHeader updateConsumerOffsetRequestHeader = new UpdateConsumerOffsetRequestHeader(); - updateConsumerOffsetRequestHeader.setConsumerGroup("consumerGroupA"); + updateConsumerOffsetRequestHeader.setConsumerGroup("groupB"); RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET, updateConsumerOffsetRequestHeader); aclClient.doBeforeRequest("", remotingCommand); ByteBuffer buf = remotingCommand.encodeHeader(); @@ -347,7 +399,7 @@ public void validateUpdateConsumerOffSetTest() { buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); buf.position(0); try { - PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876"); + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "123.4.5.6"); plainAccessValidator.validate(accessResource); } catch (RemotingCommandException e) { e.printStackTrace(); @@ -427,8 +479,12 @@ public void validateGetAllTopicConfigTest() { @Test public void addAccessAclYamlConfigTest() throws InterruptedException { - String targetFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml"; - Map backUpAclConfigMap = AclUtils.getYamlDataObject(targetFileName, Map.class); + String backupFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl_bak.yml".replace("/", File.separator); + String targetFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl.yml".replace("/", File.separator); + PlainAccessData backUpAclConfigMap = AclUtils.getYamlDataObject(backupFileName, PlainAccessData.class); + AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); plainAccessConfig.setAccessKey("rocketmq3"); @@ -436,11 +492,11 @@ public void addAccessAclYamlConfigTest() throws InterruptedException { plainAccessConfig.setWhiteRemoteAddress("192.168.0.*"); plainAccessConfig.setDefaultGroupPerm("PUB"); plainAccessConfig.setDefaultTopicPerm("SUB"); - List topicPerms = new ArrayList(); + List topicPerms = new ArrayList<>(); topicPerms.add("topicC=PUB|SUB"); topicPerms.add("topicB=PUB"); plainAccessConfig.setTopicPerms(topicPerms); - List groupPerms = new ArrayList(); + List groupPerms = new ArrayList<>(); groupPerms.add("groupB=PUB|SUB"); groupPerms.add("groupC=DENY"); plainAccessConfig.setGroupPerms(groupPerms); @@ -473,9 +529,9 @@ public void addAccessAclYamlConfigTest() throws InterruptedException { Assert.assertEquals(((List) verifyMap.get(AclConstants.CONFIG_GROUP_PERMS)).size(), 2); String aclFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml"; - Map readableMap = AclUtils.getYamlDataObject(aclFileName, Map.class); - List> dataVersions = (List>) readableMap.get(AclConstants.CONFIG_DATA_VERSION); - Assert.assertEquals(1, dataVersions.get(0).get(AclConstants.CONFIG_COUNTER)); + PlainAccessData readableMap = AclUtils.getYamlDataObject(aclFileName, PlainAccessData.class); + List dataVersions = readableMap.getDataVersion(); + Assert.assertEquals(1L, dataVersions.get(0).getCounter()); AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); } @@ -499,16 +555,21 @@ public void getAccessAclYamlConfigTest() { Assert.assertEquals(verifyMap.get(AclConstants.CONFIG_ADMIN_ROLE), true); Assert.assertEquals(verifyMap.get(AclConstants.CONFIG_WHITE_ADDR), "192.168.1.*"); - String aclFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/acl/plain_acl.yml"; + String aclFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl.yml".replace("/", File.separator); Map dataVersionMap = plainAccessValidator.getAllAclConfigVersion(); DataVersion dataVersion = dataVersionMap.get(aclFileName); Assert.assertEquals(0, dataVersion.getCounter().get()); } @Test - public void updateAccessAclYamlConfigTest() throws InterruptedException{ - String targetFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml"; - Map backUpAclConfigMap = AclUtils.getYamlDataObject(targetFileName, Map.class); + public void updateAccessAclYamlConfigTest() throws InterruptedException { + String backupFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl_bak.yml".replace("/", File.separator); + String targetFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl.yml".replace("/", File.separator); + PlainAccessData backUpAclConfigMap = AclUtils.getYamlDataObject(backupFileName, PlainAccessData.class); + AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); plainAccessConfig.setAccessKey("rocketmq3"); @@ -516,11 +577,11 @@ public void updateAccessAclYamlConfigTest() throws InterruptedException{ plainAccessConfig.setWhiteRemoteAddress("192.168.0.*"); plainAccessConfig.setDefaultGroupPerm("PUB"); plainAccessConfig.setDefaultTopicPerm("SUB"); - List topicPerms = new ArrayList(); + List topicPerms = new ArrayList<>(); topicPerms.add("topicC=PUB|SUB"); topicPerms.add("topicB=PUB"); plainAccessConfig.setTopicPerms(topicPerms); - List groupPerms = new ArrayList(); + List groupPerms = new ArrayList<>(); groupPerms.add("groupB=PUB|SUB"); groupPerms.add("groupC=DENY"); plainAccessConfig.setGroupPerms(groupPerms); @@ -536,11 +597,11 @@ public void updateAccessAclYamlConfigTest() throws InterruptedException{ plainAccessConfig1.setWhiteRemoteAddress("192.168.0.*"); plainAccessConfig1.setDefaultGroupPerm("PUB"); plainAccessConfig1.setDefaultTopicPerm("SUB"); - List topicPerms1 = new ArrayList(); + List topicPerms1 = new ArrayList<>(); topicPerms1.add("topicC=PUB|SUB"); topicPerms1.add("topicB=PUB"); plainAccessConfig1.setTopicPerms(topicPerms1); - List groupPerms1 = new ArrayList(); + List groupPerms1 = new ArrayList<>(); groupPerms1.add("groupB=PUB|SUB"); groupPerms1.add("groupC=DENY"); plainAccessConfig1.setGroupPerms(groupPerms1); @@ -572,18 +633,23 @@ public void updateAccessAclYamlConfigTest() throws InterruptedException{ Assert.assertEquals(((List) verifyMap.get(AclConstants.CONFIG_TOPIC_PERMS)).size(), 2); Assert.assertEquals(((List) verifyMap.get(AclConstants.CONFIG_GROUP_PERMS)).size(), 2); - String aclFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml"; - Map readableMap = AclUtils.getYamlDataObject(aclFileName, Map.class); - List> dataVersions = (List>) readableMap.get(AclConstants.CONFIG_DATA_VERSION); - Assert.assertEquals(2, dataVersions.get(0).get(AclConstants.CONFIG_COUNTER)); + String aclFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl.yml".replace("/", File.separator); + PlainAccessData readableMap = AclUtils.getYamlDataObject(aclFileName, PlainAccessData.class); + List dataVersions = readableMap.getDataVersion(); + Assert.assertEquals(2L, dataVersions.get(0).getCounter()); AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); } @Test public void deleteAccessAclYamlConfigTest() throws InterruptedException { - String targetFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml"; - Map backUpAclConfigMap = AclUtils.getYamlDataObject(targetFileName, Map.class); + String backupFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl_bak.yml".replace("/", File.separator); + String targetFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl.yml".replace("/", File.separator); + PlainAccessData backUpAclConfigMap = AclUtils.getYamlDataObject(backupFileName, PlainAccessData.class); + AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); plainAccessConfig.setAccessKey("rocketmq3"); @@ -591,11 +657,11 @@ public void deleteAccessAclYamlConfigTest() throws InterruptedException { plainAccessConfig.setWhiteRemoteAddress("192.168.0.*"); plainAccessConfig.setDefaultGroupPerm("PUB"); plainAccessConfig.setDefaultTopicPerm("SUB"); - List topicPerms = new ArrayList(); + List topicPerms = new ArrayList<>(); topicPerms.add("topicC=PUB|SUB"); topicPerms.add("topicB=PUB"); plainAccessConfig.setTopicPerms(topicPerms); - List groupPerms = new ArrayList(); + List groupPerms = new ArrayList<>(); groupPerms.add("groupB=PUB|SUB"); groupPerms.add("groupC=DENY"); plainAccessConfig.setGroupPerms(groupPerms); @@ -629,25 +695,31 @@ public void deleteAccessAclYamlConfigTest() throws InterruptedException { @Test public void updateGlobalWhiteRemoteAddressesTest() throws InterruptedException { - String targetFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml"; - Map backUpAclConfigMap = AclUtils.getYamlDataObject(targetFileName, Map.class); + String backupFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl_bak.yml".replace("/", File.separator); + String targetFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl.yml".replace("/", File.separator); + PlainAccessData backUpAclConfigMap = AclUtils.getYamlDataObject(backupFileName, PlainAccessData.class); + AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); List globalWhiteAddrsList = new ArrayList<>(); globalWhiteAddrsList.add("192.168.1.*"); PlainAccessValidator plainAccessValidator = new PlainAccessValidator(); - Assert.assertEquals(plainAccessValidator.updateGlobalWhiteAddrsConfig(globalWhiteAddrsList), true); + Assert.assertEquals(plainAccessValidator.updateGlobalWhiteAddrsConfig(globalWhiteAddrsList, null), true); - String aclFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml"; - Map readableMap = AclUtils.getYamlDataObject(aclFileName, Map.class); - List> dataVersions = (List>) readableMap.get(AclConstants.CONFIG_DATA_VERSION); - Assert.assertEquals(1, dataVersions.get(0).get(AclConstants.CONFIG_COUNTER)); + String aclFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl.yml".replace("/", File.separator); + PlainAccessData readableMap = AclUtils.getYamlDataObject(aclFileName, PlainAccessData.class); + List dataVersions = readableMap.getDataVersion(); + Assert.assertEquals(1L, dataVersions.get(0).getCounter()); AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); } @Test public void addYamlConfigTest() throws IOException, InterruptedException { - String fileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/acl/plain_acl_test.yml"; + String fileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/acl/plain_acl_test.yml".replace("/", File.separator); File transport = new File(fileName); transport.delete(); transport.createNewFile(); @@ -687,7 +759,8 @@ public void addYamlConfigTest() throws IOException, InterruptedException { @Test public void updateAccessAnotherAclYamlConfigTest() throws IOException, InterruptedException { - String fileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/acl/plain_acl_test.yml"; + String fileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/acl/plain_acl_test.yml".replace("/", File.separator); File transport = new File(fileName); transport.delete(); transport.createNewFile(); @@ -743,8 +816,12 @@ public void updateAccessAnotherAclYamlConfigTest() throws IOException, Interrupt @Test(expected = AclException.class) public void createAndUpdateAccessAclNullSkExceptionTest() { - String targetFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/acl/plain_acl.yml"; - Map backUpAclConfigMap = AclUtils.getYamlDataObject(targetFileName, Map.class); + String backupFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl_bak.yml".replace("/", File.separator); + String targetFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl.yml".replace("/", File.separator); + PlainAccessData backUpAclConfigMap = AclUtils.getYamlDataObject(backupFileName, PlainAccessData.class); + AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); plainAccessConfig.setAccessKey("RocketMQ33"); @@ -759,8 +836,12 @@ public void createAndUpdateAccessAclNullSkExceptionTest() { @Test public void addAccessDefaultAclYamlConfigTest() throws InterruptedException { PlainAccessValidator plainAccessValidator = new PlainAccessValidator(); - String targetFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml"; - Map backUpAclConfigMap = AclUtils.getYamlDataObject(targetFileName, Map.class); + String backupFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl_bak.yml".replace("/", File.separator); + String targetFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl.yml".replace("/", File.separator); + PlainAccessData backUpAclConfigMap = AclUtils.getYamlDataObject(backupFileName, PlainAccessData.class); + AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); plainAccessConfig.setAccessKey("watchrocketmqh"); @@ -787,16 +868,17 @@ public void addAccessDefaultAclYamlConfigTest() throws InterruptedException { Assert.assertEquals(verifyMap.get(AclConstants.CONFIG_WHITE_ADDR), "127.0.0.1"); Assert.assertEquals(verifyMap.get(AclConstants.CONFIG_ADMIN_ROLE), false); - Map readableMap = AclUtils.getYamlDataObject(targetFileName, Map.class); - List> dataVersions = (List>) readableMap.get(AclConstants.CONFIG_DATA_VERSION); - Assert.assertEquals(1, dataVersions.get(0).get(AclConstants.CONFIG_COUNTER)); + PlainAccessData readableMap = AclUtils.getYamlDataObject(targetFileName, PlainAccessData.class); + List dataVersions = readableMap.getDataVersion(); + Assert.assertEquals(1L, dataVersions.get(0).getCounter()); AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); } @Test public void deleteAccessAnotherAclYamlConfigTest() throws IOException, InterruptedException { - String fileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/acl/plain_acl_test.yml"; + String fileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/acl/plain_acl_test.yml".replace("/", File.separator); File transport = new File(fileName); transport.delete(); transport.createNewFile(); @@ -849,8 +931,12 @@ public void getAllAclConfigTest() { @Test public void updateAccessConfigEmptyPermListTest() { - String targetFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml"; - Map backUpAclConfigMap = AclUtils.getYamlDataObject(targetFileName, Map.class); + String backupFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl_bak.yml".replace("/", File.separator); + String targetFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl.yml".replace("/", File.separator); + PlainAccessData backUpAclConfigMap = AclUtils.getYamlDataObject(backupFileName, PlainAccessData.class); + AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); PlainAccessValidator plainAccessValidator = new PlainAccessValidator(); PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); @@ -877,8 +963,12 @@ public void updateAccessConfigEmptyPermListTest() { @Test public void updateAccessConfigEmptyWhiteRemoteAddressTest() { - String targetFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml"; - Map backUpAclConfigMap = AclUtils.getYamlDataObject(targetFileName, Map.class); + String backupFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl_bak.yml".replace("/", File.separator); + String targetFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/plain_acl.yml".replace("/", File.separator); + PlainAccessData backUpAclConfigMap = AclUtils.getYamlDataObject(backupFileName, PlainAccessData.class); + AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); PlainAccessValidator plainAccessValidator = new PlainAccessValidator(); PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); @@ -902,4 +992,121 @@ public void updateAccessConfigEmptyWhiteRemoteAddressTest() { plainAccessValidator.deleteAccessConfig(accessKey); AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); } + + @Test + public void deleteAccessAclToEmptyTest() { + final String bakAclFileProp = System.getProperty("rocketmq.acl.plain.file"); + System.setProperty("rocketmq.acl.plain.file", "conf/empty.yml".replace("/", File.separator)); + PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); + plainAccessConfig.setAccessKey("deleteAccessAclToEmpty"); + plainAccessConfig.setSecretKey("12345678"); + + PlainAccessValidator plainAccessValidator = new PlainAccessValidator(); + plainAccessValidator.updateAccessConfig(plainAccessConfig); + boolean success = plainAccessValidator.deleteAccessConfig("deleteAccessAclToEmpty"); + if (null != bakAclFileProp) { + System.setProperty("rocketmq.acl.plain.file", bakAclFileProp); + } else { + System.clearProperty("rocketmq.acl.plain.file"); + } + Assert.assertTrue(success); + } + + @Test + public void testValidateAfterUpdateAccessConfig() throws NoSuchFieldException, IllegalAccessException { + String targetFileName = System.getProperty("rocketmq.home.dir") + + File.separator + "conf/update.yml".replace("/", File.separator); + System.setProperty("rocketmq.acl.plain.file", "conf/update.yml".replace("/", File.separator)); + PlainAccessValidator plainAccessValidator = new PlainAccessValidator(); + PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); + String accessKey = "updateAccessConfig"; + String secretKey = "123456789111"; + plainAccessConfig.setAccessKey(accessKey); + plainAccessConfig.setSecretKey(secretKey); + plainAccessConfig.setAdmin(true); + // update + plainAccessValidator.updateAccessConfig(plainAccessConfig); + // call load + Class clazz = PlainAccessValidator.class; + Field f = clazz.getDeclaredField("aclPlugEngine"); + f.setAccessible(true); + PlainPermissionManager aclPlugEngine = (PlainPermissionManager) f.get(plainAccessValidator); + aclPlugEngine.load(targetFileName); + + // call validate + PullMessageRequestHeader pullMessageRequestHeader = new PullMessageRequestHeader(); + pullMessageRequestHeader.setTopic("topicC"); + pullMessageRequestHeader.setConsumerGroup("consumerGroupA"); + RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, pullMessageRequestHeader); + + AclClientRPCHook aclClient = new AclClientRPCHook(new SessionCredentials(accessKey, secretKey)); + aclClient.doBeforeRequest("", remotingCommand); + ByteBuffer buf = remotingCommand.encodeHeader(); + buf.getInt(); + buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf); + buf.position(0); + try { + PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "1.1.1.1:9876"); + plainAccessValidator.validate(accessResource); + } catch (RemotingCommandException e) { + e.printStackTrace(); + Assert.fail("Should not throw IOException"); + } finally { + System.setProperty("rocketmq.acl.plain.file", "conf/plain_acl.yml".replace("/", File.separator)); + } + } + + /** + * Fixme: this test case is not thread safe. The design itself is buggy. + * @throws IOException + */ + @Test + public void testUpdateSpecifiedAclFileGlobalWhiteAddrsConfig() throws IOException { + String folder = "update_global_white_addr"; + File home = AclTestHelper.copyResources(folder); + System.setProperty("rocketmq.home.dir", home.getAbsolutePath()); + System.setProperty("rocketmq.acl.plain.file", "/conf/plain_acl.yml".replace("/", File.separator)); + + String targetFileName = Joiner.on(File.separator).join(new String[]{home.getAbsolutePath(), "conf", "plain_acl.yml"}); + PlainAccessData backUpAclConfigMap = AclUtils.getYamlDataObject(targetFileName, PlainAccessData.class); + + String targetFileName1 = Joiner.on(File.separator).join(new String[]{home.getAbsolutePath(), "conf", "acl", "plain_acl.yml"}); + PlainAccessData backUpAclConfigMap1 = AclUtils.getYamlDataObject(targetFileName1, PlainAccessData.class); + + String targetFileName2 = Joiner.on(File.separator).join(new String[]{home.getAbsolutePath(), "conf", "acl", "empty.yml"}); + PlainAccessData backUpAclConfigMap2 = AclUtils.getYamlDataObject(targetFileName2, PlainAccessData.class); + + PlainAccessValidator plainAccessValidator = new PlainAccessValidator(); + List globalWhiteAddrsList1 = new ArrayList<>(); + globalWhiteAddrsList1.add("10.10.154.1"); + List globalWhiteAddrsList2 = new ArrayList<>(); + globalWhiteAddrsList2.add("10.10.154.2"); + List globalWhiteAddrsList3 = new ArrayList<>(); + globalWhiteAddrsList3.add("10.10.154.3"); + + //Test parameter p is null + plainAccessValidator.updateGlobalWhiteAddrsConfig(globalWhiteAddrsList1, null); + String defaultAclFile = targetFileName; + PlainAccessData defaultAclFileMap = AclUtils.getYamlDataObject(defaultAclFile, PlainAccessData.class); + List defaultAclFileGlobalWhiteAddrList = defaultAclFileMap.getGlobalWhiteRemoteAddresses(); + Assert.assertTrue(defaultAclFileGlobalWhiteAddrList.contains("10.10.154.1")); + //Test parameter p is not null + plainAccessValidator.updateGlobalWhiteAddrsConfig(globalWhiteAddrsList2, targetFileName1); + PlainAccessData aclFileMap1 = AclUtils.getYamlDataObject(targetFileName1, PlainAccessData.class); + List aclFileGlobalWhiteAddrList1 = aclFileMap1.getGlobalWhiteRemoteAddresses(); + Assert.assertTrue(aclFileGlobalWhiteAddrList1.contains("10.10.154.2")); + //Test parameter p is not null, but the file does not have globalWhiteRemoteAddresses + plainAccessValidator.updateGlobalWhiteAddrsConfig(globalWhiteAddrsList3, targetFileName2); + PlainAccessData aclFileMap2 = AclUtils.getYamlDataObject(targetFileName2, PlainAccessData.class); + List aclFileGlobalWhiteAddrList2 = aclFileMap2.getGlobalWhiteRemoteAddresses(); + Assert.assertTrue(aclFileGlobalWhiteAddrList2.contains("10.10.154.3")); + + AclUtils.writeDataObject(targetFileName, backUpAclConfigMap); + AclUtils.writeDataObject(targetFileName1, backUpAclConfigMap1); + AclUtils.writeDataObject(targetFileName2, backUpAclConfigMap2); + + AclTestHelper.recursiveDelete(home); + } + + } diff --git a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainPermissionManagerTest.java b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainPermissionManagerTest.java index d2dabc0b2e7..941d8c77923 100644 --- a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainPermissionManagerTest.java +++ b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainPermissionManagerTest.java @@ -16,34 +16,46 @@ */ package org.apache.rocketmq.acl.plain; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import com.google.common.base.Joiner; import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.rocketmq.acl.common.AclConstants; import org.apache.rocketmq.acl.common.AclException; import org.apache.rocketmq.acl.common.AclUtils; import org.apache.rocketmq.acl.common.Permission; +import org.apache.rocketmq.common.AclConfig; import org.apache.rocketmq.common.PlainAccessConfig; +import org.apache.rocketmq.remoting.protocol.DataVersion; +import org.assertj.core.api.Assertions; +import org.assertj.core.util.Lists; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + public class PlainPermissionManagerTest { PlainPermissionManager plainPermissionManager; - PlainAccessResource PUBPlainAccessResource; - PlainAccessResource SUBPlainAccessResource; - PlainAccessResource ANYPlainAccessResource; - PlainAccessResource DENYPlainAccessResource; + PlainAccessResource pubPlainAccessResource; + PlainAccessResource subPlainAccessResource; + PlainAccessResource anyPlainAccessResource; + PlainAccessResource denyPlainAccessResource; PlainAccessResource plainAccessResource = new PlainAccessResource(); PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); Set adminCode = new HashSet<>(); + private static final String DEFAULT_TOPIC = "topic-acl"; + + private File confHome; + @Before public void init() throws NoSuchFieldException, SecurityException, IOException { // UPDATE_AND_CREATE_TOPIC @@ -57,16 +69,15 @@ public void init() throws NoSuchFieldException, SecurityException, IOException { // DELETE_SUBSCRIPTIONGROUP adminCode.add(207); - PUBPlainAccessResource = clonePlainAccessResource(Permission.PUB); - SUBPlainAccessResource = clonePlainAccessResource(Permission.SUB); - ANYPlainAccessResource = clonePlainAccessResource(Permission.ANY); - DENYPlainAccessResource = clonePlainAccessResource(Permission.DENY); - - File file = new File("src/test/resources"); - System.setProperty("rocketmq.home.dir", file.getAbsolutePath()); + pubPlainAccessResource = clonePlainAccessResource(Permission.PUB); + subPlainAccessResource = clonePlainAccessResource(Permission.SUB); + anyPlainAccessResource = clonePlainAccessResource(Permission.ANY); + denyPlainAccessResource = clonePlainAccessResource(Permission.DENY); + String folder = "conf"; + confHome = AclTestHelper.copyResources(folder, true); + System.setProperty("rocketmq.home.dir", confHome.getAbsolutePath()); plainPermissionManager = new PlainPermissionManager(); - } public PlainAccessResource clonePlainAccessResource(byte perm) { @@ -107,7 +118,7 @@ public void buildPlainAccessResourceTest() { plainAccessResource = plainPermissionManager.buildPlainAccessResource(plainAccess); Assert.assertEquals(plainAccessResource.isAdmin(), true); - List groups = new ArrayList(); + List groups = new ArrayList<>(); groups.add("groupA=DENY"); groups.add("groupB=PUB|SUB"); groups.add("groupC=PUB"); @@ -120,7 +131,7 @@ public void buildPlainAccessResourceTest() { Assert.assertEquals(resourcePermMap.get(PlainAccessResource.getRetryTopic("groupB")).byteValue(), Permission.PUB | Permission.SUB); Assert.assertEquals(resourcePermMap.get(PlainAccessResource.getRetryTopic("groupC")).byteValue(), Permission.PUB); - List topics = new ArrayList(); + List topics = new ArrayList<>(); topics.add("topicA=DENY"); topics.add("topicB=PUB|SUB"); topics.add("topicC=PUB"); @@ -138,7 +149,7 @@ public void buildPlainAccessResourceTest() { public void checkPermAdmin() { PlainAccessResource plainAccessResource = new PlainAccessResource(); plainAccessResource.setRequestCode(17); - plainPermissionManager.checkPerm(plainAccessResource, PUBPlainAccessResource); + plainPermissionManager.checkPerm(plainAccessResource, pubPlainAccessResource); } @Test @@ -146,15 +157,15 @@ public void checkPerm() { PlainAccessResource plainAccessResource = new PlainAccessResource(); plainAccessResource.addResourceAndPerm("topicA", Permission.PUB); - plainPermissionManager.checkPerm(plainAccessResource, PUBPlainAccessResource); + plainPermissionManager.checkPerm(plainAccessResource, pubPlainAccessResource); plainAccessResource.addResourceAndPerm("topicB", Permission.SUB); - plainPermissionManager.checkPerm(plainAccessResource, ANYPlainAccessResource); + plainPermissionManager.checkPerm(plainAccessResource, anyPlainAccessResource); plainAccessResource = new PlainAccessResource(); plainAccessResource.addResourceAndPerm("topicB", Permission.SUB); - plainPermissionManager.checkPerm(plainAccessResource, SUBPlainAccessResource); + plainPermissionManager.checkPerm(plainAccessResource, subPlainAccessResource); plainAccessResource.addResourceAndPerm("topicA", Permission.PUB); - plainPermissionManager.checkPerm(plainAccessResource, ANYPlainAccessResource); + plainPermissionManager.checkPerm(plainAccessResource, anyPlainAccessResource); } @@ -163,7 +174,7 @@ public void checkErrorPermDefaultValueNotMatch() { plainAccessResource = new PlainAccessResource(); plainAccessResource.addResourceAndPerm("topicF", Permission.PUB); - plainPermissionManager.checkPerm(plainAccessResource, SUBPlainAccessResource); + plainPermissionManager.checkPerm(plainAccessResource, subPlainAccessResource); } @Test(expected = AclException.class) @@ -190,7 +201,6 @@ public void passWordThanTest() { plainPermissionManager.buildPlainAccessResource(plainAccessConfig); } - @SuppressWarnings("unchecked") @Test public void cleanAuthenticationInfoTest() throws IllegalAccessException { @@ -201,7 +211,6 @@ public void cleanAuthenticationInfoTest() throws IllegalAccessException { plainPermissionManager.clearPermissionInfo(); plainAccessResourceMap = (Map>) FieldUtils.readDeclaredField(plainPermissionManager, "aclPlainAccessResourceMap", true); Assert.assertTrue(plainAccessResourceMap.isEmpty()); - // RemoveDataVersionFromYamlFile("src/test/resources/conf/plain_acl.yml"); } @Test @@ -209,15 +218,11 @@ public void isWatchStartTest() { PlainPermissionManager plainPermissionManager = new PlainPermissionManager(); Assert.assertTrue(plainPermissionManager.isWatchStart()); - // RemoveDataVersionFromYamlFile("src/test/resources/conf/plain_acl.yml"); } @Test public void testWatch() throws IOException, IllegalAccessException, InterruptedException { - File file = new File("src/test/resources"); - System.setProperty("rocketmq.home.dir", file.getAbsolutePath()); - - String fileName = System.getProperty("rocketmq.home.dir") + File.separator + "/conf/acl/plain_acl_test.yml"; + String fileName = Joiner.on(File.separator).join(new String[]{System.getProperty("rocketmq.home.dir"), "conf", "acl", "plain_acl_test.yml"}); File transport = new File(fileName); transport.delete(); transport.createNewFile(); @@ -246,13 +251,11 @@ public void testWatch() throws IOException, IllegalAccessException, InterruptedE } - Map updatedMap = AclUtils.getYamlDataObject(fileName, Map.class); - List> accounts = (List>) updatedMap.get("accounts"); - accounts.get(0).remove("accessKey"); - accounts.get(0).remove("secretKey"); - accounts.get(0).put("accessKey", "watchrocketmq1y"); - accounts.get(0).put("secretKey", "88888888"); - accounts.get(0).put("admin", "false"); + PlainAccessData updatedMap = AclUtils.getYamlDataObject(fileName, PlainAccessData.class); + List accounts = updatedMap.getAccounts(); + accounts.get(0).setAccessKey("watchrocketmq1y"); + accounts.get(0).setSecretKey("88888888"); + accounts.get(0).setAdmin(false); // Update file and flush to yaml file AclUtils.writeDataObject(fileName, updatedMap); @@ -266,6 +269,111 @@ public void testWatch() throws IOException, IllegalAccessException, InterruptedE } transport.delete(); - System.setProperty("rocketmq.home.dir", "src/test/resources"); } + + @Test + public void updateAccessConfigTest() { + Assert.assertThrows(AclException.class, () -> plainPermissionManager.updateAccessConfig(null)); + + plainAccessConfig.setAccessKey("admin_test"); + // Invalid parameter + plainAccessConfig.setSecretKey("123456"); + plainAccessConfig.setAdmin(true); + Assert.assertThrows(AclException.class, () -> plainPermissionManager.updateAccessConfig(plainAccessConfig)); + + plainAccessConfig.setSecretKey("12345678"); + // Invalid parameter + plainAccessConfig.setGroupPerms(Lists.newArrayList("groupA!SUB")); + Assert.assertThrows(AclException.class, () -> plainPermissionManager.updateAccessConfig(plainAccessConfig)); + + // first update + plainAccessConfig.setGroupPerms(Lists.newArrayList("groupA=SUB")); + plainPermissionManager.updateAccessConfig(plainAccessConfig); + + // second update + plainAccessConfig.setTopicPerms(Lists.newArrayList("topicA=SUB")); + plainPermissionManager.updateAccessConfig(plainAccessConfig); + } + + @Test + public void getAllAclFilesTest() { + final List notExistList = plainPermissionManager.getAllAclFiles("aa/bb"); + Assertions.assertThat(notExistList).isEmpty(); + final List files = plainPermissionManager.getAllAclFiles(confHome.getAbsolutePath()); + Assertions.assertThat(files).isNotEmpty(); + } + + @Test + public void loadTest() { + plainPermissionManager.load(); + final Map map = plainPermissionManager.getDataVersionMap(); + Assertions.assertThat(map).isNotEmpty(); + } + + @Test + public void updateAclConfigFileVersionTest() { + String aclFileName = "test_plain_acl"; + PlainAccessData updateAclConfigMap = new PlainAccessData(); + List versionElement = new ArrayList<>(); + PlainAccessData.DataVersion accountsMap = new PlainAccessData.DataVersion(); + accountsMap.setCounter(1); + accountsMap.setTimestamp(System.currentTimeMillis()); + versionElement.add(accountsMap); + + updateAclConfigMap.setDataVersion(versionElement); + final PlainAccessData map = plainPermissionManager.updateAclConfigFileVersion(aclFileName, updateAclConfigMap); + final List version = map.getDataVersion(); + Assert.assertEquals(2L, version.get(0).getCounter()); + } + + @Test + public void createAclAccessConfigMapTest() { + PlainAccessConfig existedAccountMap = new PlainAccessConfig(); + plainAccessConfig.setAccessKey("admin123"); + plainAccessConfig.setSecretKey("12345678"); + plainAccessConfig.setWhiteRemoteAddress("192.168.1.1"); + plainAccessConfig.setAdmin(false); + plainAccessConfig.setDefaultGroupPerm(AclConstants.SUB_PUB); + plainAccessConfig.setTopicPerms(Arrays.asList(DEFAULT_TOPIC + "=" + AclConstants.PUB)); + plainAccessConfig.setGroupPerms(Lists.newArrayList("groupA=SUB")); + + final PlainAccessConfig map = plainPermissionManager.createAclAccessConfigMap(existedAccountMap, plainAccessConfig); + Assert.assertEquals(AclConstants.SUB_PUB, map.getDefaultGroupPerm()); + Assert.assertEquals("groupA=SUB", map.getGroupPerms().get(0)); + Assert.assertEquals("12345678", map.getSecretKey()); + Assert.assertEquals("admin123", map.getAccessKey()); + Assert.assertEquals("192.168.1.1", map.getWhiteRemoteAddress()); + Assert.assertEquals("topic-acl=PUB", map.getTopicPerms().get(0)); + Assert.assertEquals(false, map.isAdmin()); + } + + @Test + public void deleteAccessConfigTest() throws InterruptedException { + // delete not exist accessConfig + final boolean flag1 = plainPermissionManager.deleteAccessConfig("test_delete"); + assert !flag1; + + plainAccessConfig.setAccessKey("test_delete"); + plainAccessConfig.setSecretKey("12345678"); + plainAccessConfig.setWhiteRemoteAddress("192.168.1.1"); + plainAccessConfig.setAdmin(false); + plainAccessConfig.setDefaultGroupPerm(AclConstants.SUB_PUB); + plainAccessConfig.setTopicPerms(Arrays.asList(DEFAULT_TOPIC + "=" + AclConstants.PUB)); + plainAccessConfig.setGroupPerms(Lists.newArrayList("groupA=SUB")); + plainPermissionManager.updateAccessConfig(plainAccessConfig); + + //delete existed accessConfig + final boolean flag2 = plainPermissionManager.deleteAccessConfig("test_delete"); + assert flag2; + + } + + @Test + public void updateGlobalWhiteAddrsConfigTest() { + final boolean flag = plainPermissionManager.updateGlobalWhiteAddrsConfig(Lists.newArrayList("192.168.1.2")); + assert flag; + final AclConfig config = plainPermissionManager.getAllAclConfig(); + Assert.assertEquals(true, config.getGlobalWhiteAddrs().contains("192.168.1.2")); + } + } diff --git a/acl/src/test/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyTest.java b/acl/src/test/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyTest.java index 87eb37bdf2f..df7dd0c5460 100644 --- a/acl/src/test/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyTest.java +++ b/acl/src/test/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyTest.java @@ -25,7 +25,7 @@ public class RemoteAddressStrategyTest { RemoteAddressStrategyFactory remoteAddressStrategyFactory = new RemoteAddressStrategyFactory(); @Test - public void netaddressStrategyFactoryExceptionTest() { + public void netAddressStrategyFactoryExceptionTest() { PlainAccessResource plainAccessResource = new PlainAccessResource(); remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); Assert.assertEquals(remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource).getClass(), @@ -33,7 +33,7 @@ public void netaddressStrategyFactoryExceptionTest() { } @Test - public void netaddressStrategyFactoryTest() { + public void netAddressStrategyFactoryTest() { PlainAccessResource plainAccessResource = new PlainAccessResource(); plainAccessResource.setWhiteRemoteAddress("*"); @@ -114,19 +114,19 @@ public void verifyTest() { } @Test - public void nullNetaddressStrategyTest() { + public void nullNetAddressStrategyTest() { boolean isMatch = RemoteAddressStrategyFactory.NULL_NET_ADDRESS_STRATEGY.match(new PlainAccessResource()); Assert.assertTrue(isMatch); } @Test - public void blankNetaddressStrategyTest() { + public void blankNetAddressStrategyTest() { boolean isMatch = RemoteAddressStrategyFactory.BLANK_NET_ADDRESS_STRATEGY.match(new PlainAccessResource()); Assert.assertFalse(isMatch); } @Test - public void oneNetaddressStrategyTest() { + public void oneNetAddressStrategyTest() { PlainAccessResource plainAccessResource = new PlainAccessResource(); plainAccessResource.setWhiteRemoteAddress("127.0.0.1"); RemoteAddressStrategy remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); @@ -164,15 +164,15 @@ public void oneNetaddressStrategyTest() { } @Test - public void multipleNetaddressStrategyTest() { + public void multipleNetAddressStrategyTest() { PlainAccessResource plainAccessResource = new PlainAccessResource(); plainAccessResource.setWhiteRemoteAddress("127.0.0.1,127.0.0.2,127.0.0.3"); RemoteAddressStrategy remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - multipleNetaddressStrategyTest(remoteAddressStrategy); + multipleNetAddressStrategyTest(remoteAddressStrategy); plainAccessResource.setWhiteRemoteAddress("127.0.0.{1,2,3}"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - multipleNetaddressStrategyTest(remoteAddressStrategy); + multipleNetAddressStrategyTest(remoteAddressStrategy); plainAccessResource.setWhiteRemoteAddress("192.100-150.*.*"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); @@ -183,16 +183,16 @@ public void multipleNetaddressStrategyTest() { plainAccessResource = new PlainAccessResource(); plainAccessResource.setWhiteRemoteAddress("1050::0005:0600:300c:1,1050::0005:0600:300c:2,1050::0005:0600:300c:3"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - multipleIPv6NetaddressStrategyTest(remoteAddressStrategy); + multipleIPv6NetAddressStrategyTest(remoteAddressStrategy); plainAccessResource.setWhiteRemoteAddress("1050::0005:0600:300c:{1,2,3}"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - multipleIPv6NetaddressStrategyTest(remoteAddressStrategy); + multipleIPv6NetAddressStrategyTest(remoteAddressStrategy); } @Test(expected = AclException.class) - public void multipleNetaddressStrategyExceptionTest() { + public void multipleNetAddressStrategyExceptionTest() { PlainAccessResource plainAccessResource = new PlainAccessResource(); plainAccessResource.setWhiteRemoteAddress("127.0.0.1,2,3}"); remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); @@ -210,7 +210,7 @@ public void multipleNetaddressStrategyExceptionTest() { remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); } - private void multipleNetaddressStrategyTest(RemoteAddressStrategy remoteAddressStrategy) { + private void multipleNetAddressStrategyTest(RemoteAddressStrategy remoteAddressStrategy) { PlainAccessResource plainAccessResource = new PlainAccessResource(); plainAccessResource.setWhiteRemoteAddress("127.0.0.1"); boolean match = remoteAddressStrategy.match(plainAccessResource); @@ -234,7 +234,7 @@ private void multipleNetaddressStrategyTest(RemoteAddressStrategy remoteAddressS } - private void multipleIPv6NetaddressStrategyTest(RemoteAddressStrategy remoteAddressStrategy) { + private void multipleIPv6NetAddressStrategyTest(RemoteAddressStrategy remoteAddressStrategy) { PlainAccessResource plainAccessResource = new PlainAccessResource(); plainAccessResource.setWhiteRemoteAddress("1050:0000:0000:0000:0005:0600:300c:1"); boolean match = remoteAddressStrategy.match(plainAccessResource); @@ -259,57 +259,57 @@ private void multipleIPv6NetaddressStrategyTest(RemoteAddressStrategy remoteAddr } @Test - public void rangeNetaddressStrategyTest() { + public void rangeNetAddressStrategyTest() { String head = "127.0.0."; PlainAccessResource plainAccessResource = new PlainAccessResource(); plainAccessResource.setWhiteRemoteAddress("127.0.0.1-200"); RemoteAddressStrategy remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - rangeNetaddressStrategyTest(remoteAddressStrategy, head, 1, 200, true); + rangeNetAddressStrategyTest(remoteAddressStrategy, head, 1, 200, true); plainAccessResource.setWhiteRemoteAddress("127.0.0.*"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - rangeNetaddressStrategyTest(remoteAddressStrategy, head, 0, 255, true); + rangeNetAddressStrategyTest(remoteAddressStrategy, head, 0, 255, true); plainAccessResource.setWhiteRemoteAddress("127.0.1-200.*"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - rangeNetaddressStrategyThirdlyTest(remoteAddressStrategy, head, 1, 200); + rangeNetAddressStrategyThirdlyTest(remoteAddressStrategy, head, 1, 200); plainAccessResource.setWhiteRemoteAddress("127.*.*.*"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - rangeNetaddressStrategyTest(remoteAddressStrategy, head, 0, 255, true); + rangeNetAddressStrategyTest(remoteAddressStrategy, head, 0, 255, true); plainAccessResource.setWhiteRemoteAddress("127.1-150.*.*"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - rangeNetaddressStrategyThirdlyTest(remoteAddressStrategy, head, 1, 200); + rangeNetAddressStrategyThirdlyTest(remoteAddressStrategy, head, 1, 200); // IPv6 test head = "1050::0005:0600:300c:"; plainAccessResource = new PlainAccessResource(); plainAccessResource.setWhiteRemoteAddress("1050::0005:0600:300c:1-200"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - rangeIPv6NetaddressStrategyTest(remoteAddressStrategy, head, "1", "200", true); + rangeIPv6NetAddressStrategyTest(remoteAddressStrategy, head, "1", "200", true); plainAccessResource.setWhiteRemoteAddress("1050::0005:0600:300c:*"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - rangeIPv6NetaddressStrategyTest(remoteAddressStrategy, head, "0", "ffff", true); + rangeIPv6NetAddressStrategyTest(remoteAddressStrategy, head, "0", "ffff", true); plainAccessResource.setWhiteRemoteAddress("1050::0005:0600:3001:*"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - rangeIPv6NetaddressStrategyTest(remoteAddressStrategy, head, "0", "ffff", false); + rangeIPv6NetAddressStrategyTest(remoteAddressStrategy, head, "0", "ffff", false); head = "1050::0005:0600:300c:1:"; plainAccessResource.setWhiteRemoteAddress("1050::0005:0600:300c:1-200:*"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - rangeIPv6NetaddressStrategyTest(remoteAddressStrategy, head, "0", "ffff", true); + rangeIPv6NetAddressStrategyTest(remoteAddressStrategy, head, "0", "ffff", true); head = "1050::0005:0600:300c:201:"; plainAccessResource.setWhiteRemoteAddress("1050::0005:0600:300c:1-200:*"); remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); - rangeIPv6NetaddressStrategyTest(remoteAddressStrategy, head, "0", "ffff", false); + rangeIPv6NetAddressStrategyTest(remoteAddressStrategy, head, "0", "ffff", false); } - private void rangeNetaddressStrategyTest(RemoteAddressStrategy remoteAddressStrategy, String head, int start, + private void rangeNetAddressStrategyTest(RemoteAddressStrategy remoteAddressStrategy, String head, int start, int end, boolean isFalse) { PlainAccessResource plainAccessResource = new PlainAccessResource(); @@ -325,18 +325,18 @@ private void rangeNetaddressStrategyTest(RemoteAddressStrategy remoteAddressStra } } - private void rangeNetaddressStrategyThirdlyTest(RemoteAddressStrategy remoteAddressStrategy, String head, int start, + private void rangeNetAddressStrategyThirdlyTest(RemoteAddressStrategy remoteAddressStrategy, String head, int start, int end) { String newHead; for (int i = -10; i < 300; i++) { newHead = head + i; if (i >= start && i <= end) { - rangeNetaddressStrategyTest(remoteAddressStrategy, newHead, 0, 255, false); + rangeNetAddressStrategyTest(remoteAddressStrategy, newHead, 0, 255, false); } } } - private void rangeIPv6NetaddressStrategyTest(RemoteAddressStrategy remoteAddressStrategy, String head, String start, + private void rangeIPv6NetAddressStrategyTest(RemoteAddressStrategy remoteAddressStrategy, String head, String start, String end, boolean isFalse) { PlainAccessResource plainAccessResource = new PlainAccessResource(); @@ -356,23 +356,23 @@ private void rangeIPv6NetaddressStrategyTest(RemoteAddressStrategy remoteAddress } @Test(expected = AclException.class) - public void rangeNetaddressStrategyExceptionStartGreaterEndTest() { - rangeNetaddressStrategyExceptionTest("127.0.0.2-1"); + public void rangeNetAddressStrategyExceptionStartGreaterEndTest() { + rangeNetAddressStrategyExceptionTest("127.0.0.2-1"); } @Test(expected = AclException.class) - public void rangeNetaddressStrategyExceptionScopeTest() { - rangeNetaddressStrategyExceptionTest("127.0.0.-1-200"); + public void rangeNetAddressStrategyExceptionScopeTest() { + rangeNetAddressStrategyExceptionTest("127.0.0.-1-200"); } @Test(expected = AclException.class) - public void rangeNetaddressStrategyExceptionScopeTwoTest() { - rangeNetaddressStrategyExceptionTest("127.0.0.0-256"); + public void rangeNetAddressStrategyExceptionScopeTwoTest() { + rangeNetAddressStrategyExceptionTest("127.0.0.0-256"); } - private void rangeNetaddressStrategyExceptionTest(String netaddress) { + private void rangeNetAddressStrategyExceptionTest(String netAddress) { PlainAccessResource plainAccessResource = new PlainAccessResource(); - plainAccessResource.setWhiteRemoteAddress(netaddress); + plainAccessResource.setWhiteRemoteAddress(netAddress); remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource); } diff --git a/acl/src/test/resources/both_acl_file_folder_conf/conf/acl/plain_acl.yml b/acl/src/test/resources/both_acl_file_folder_conf/conf/acl/plain_acl.yml new file mode 100644 index 00000000000..cf4ea7f4a5b --- /dev/null +++ b/acl/src/test/resources/both_acl_file_folder_conf/conf/acl/plain_acl.yml @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +## no global white addresses in this file, define them in ../plain_acl.yml +accounts: + - accessKey: RocketMQ + secretKey: 12345678 + whiteRemoteAddress: 192.168.0.* + admin: false + defaultTopicPerm: DENY + defaultGroupPerm: SUB + topicPerms: + - topicA=DENY + - topicB=PUB|SUB + - topicC=SUB + groupPerms: + # the group should convert to retry topic + - groupA=DENY + - groupB=SUB + - groupC=SUB + + - accessKey: rocketmq2 + secretKey: 12345678 + whiteRemoteAddress: 192.168.1.* + # if it is admin, it could access all resources + admin: true + diff --git a/acl/src/test/resources/both_acl_file_folder_conf/conf/plain_acl.yml b/acl/src/test/resources/both_acl_file_folder_conf/conf/plain_acl.yml new file mode 100644 index 00000000000..41afea097ae --- /dev/null +++ b/acl/src/test/resources/both_acl_file_folder_conf/conf/plain_acl.yml @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + +globalWhiteRemoteAddresses: + - 10.10.103.* + - 192.168.0.* + diff --git a/acl/src/test/resources/conf/plain_acl.yml b/acl/src/test/resources/conf/plain_acl.yml index 2c24795ff64..59bd6d4ff29 100644 --- a/acl/src/test/resources/conf/plain_acl.yml +++ b/acl/src/test/resources/conf/plain_acl.yml @@ -18,7 +18,6 @@ globalWhiteRemoteAddresses: - 10.10.103.* - 192.168.0.* - accounts: - accessKey: RocketMQ secretKey: 12345678 @@ -31,14 +30,10 @@ accounts: - topicB=PUB|SUB - topicC=SUB groupPerms: - # the group should convert to retry topic - groupA=DENY - groupB=SUB - groupC=SUB - - accessKey: rocketmq2 secretKey: 12345678 whiteRemoteAddress: 192.168.1.* - # if it is admin, it could access all resources admin: true - diff --git a/acl/src/test/resources/conf/plain_acl_bak.yml b/acl/src/test/resources/conf/plain_acl_bak.yml new file mode 100644 index 00000000000..59bd6d4ff29 --- /dev/null +++ b/acl/src/test/resources/conf/plain_acl_bak.yml @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + +globalWhiteRemoteAddresses: +- 10.10.103.* +- 192.168.0.* +accounts: +- accessKey: RocketMQ + secretKey: 12345678 + whiteRemoteAddress: 192.168.0.* + admin: false + defaultTopicPerm: DENY + defaultGroupPerm: SUB + topicPerms: + - topicA=DENY + - topicB=PUB|SUB + - topicC=SUB + groupPerms: + - groupA=DENY + - groupB=SUB + - groupC=SUB +- accessKey: rocketmq2 + secretKey: 12345678 + whiteRemoteAddress: 192.168.1.* + admin: true diff --git a/acl/src/test/resources/empty_acl_folder_conf/conf/plain_acl.yml b/acl/src/test/resources/empty_acl_folder_conf/conf/plain_acl.yml new file mode 100644 index 00000000000..6ade46723b7 --- /dev/null +++ b/acl/src/test/resources/empty_acl_folder_conf/conf/plain_acl.yml @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +globalWhiteRemoteAddresses: + - 10.10.103.* + - 192.168.0.* diff --git a/acl/src/test/resources/only_acl_folder_conf/conf/acl/plain_acl.yml b/acl/src/test/resources/only_acl_folder_conf/conf/acl/plain_acl.yml new file mode 100644 index 00000000000..cf4ea7f4a5b --- /dev/null +++ b/acl/src/test/resources/only_acl_folder_conf/conf/acl/plain_acl.yml @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +## no global white addresses in this file, define them in ../plain_acl.yml +accounts: + - accessKey: RocketMQ + secretKey: 12345678 + whiteRemoteAddress: 192.168.0.* + admin: false + defaultTopicPerm: DENY + defaultGroupPerm: SUB + topicPerms: + - topicA=DENY + - topicB=PUB|SUB + - topicC=SUB + groupPerms: + # the group should convert to retry topic + - groupA=DENY + - groupB=SUB + - groupC=SUB + + - accessKey: rocketmq2 + secretKey: 12345678 + whiteRemoteAddress: 192.168.1.* + # if it is admin, it could access all resources + admin: true + diff --git a/acl/src/test/resources/logback-test.xml b/acl/src/test/resources/rmq.logback-test.xml similarity index 66% rename from acl/src/test/resources/logback-test.xml rename to acl/src/test/resources/rmq.logback-test.xml index e556c649e52..8695d52d57c 100644 --- a/acl/src/test/resources/logback-test.xml +++ b/acl/src/test/resources/rmq.logback-test.xml @@ -17,18 +17,20 @@ --> - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n - UTF-8 - + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + - - + + - - + + + - + \ No newline at end of file diff --git a/acl/src/test/resources/update_global_white_addr/conf/acl/empty.yml b/acl/src/test/resources/update_global_white_addr/conf/acl/empty.yml new file mode 100644 index 00000000000..52ff50c2bef --- /dev/null +++ b/acl/src/test/resources/update_global_white_addr/conf/acl/empty.yml @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + +accounts: [] diff --git a/acl/src/test/resources/update_global_white_addr/conf/acl/plain_acl.yml b/acl/src/test/resources/update_global_white_addr/conf/acl/plain_acl.yml new file mode 100644 index 00000000000..59bd6d4ff29 --- /dev/null +++ b/acl/src/test/resources/update_global_white_addr/conf/acl/plain_acl.yml @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + +globalWhiteRemoteAddresses: +- 10.10.103.* +- 192.168.0.* +accounts: +- accessKey: RocketMQ + secretKey: 12345678 + whiteRemoteAddress: 192.168.0.* + admin: false + defaultTopicPerm: DENY + defaultGroupPerm: SUB + topicPerms: + - topicA=DENY + - topicB=PUB|SUB + - topicC=SUB + groupPerms: + - groupA=DENY + - groupB=SUB + - groupC=SUB +- accessKey: rocketmq2 + secretKey: 12345678 + whiteRemoteAddress: 192.168.1.* + admin: true diff --git a/acl/src/test/resources/update_global_white_addr/conf/plain_acl.yml b/acl/src/test/resources/update_global_white_addr/conf/plain_acl.yml new file mode 100644 index 00000000000..64c6e34169c --- /dev/null +++ b/acl/src/test/resources/update_global_white_addr/conf/plain_acl.yml @@ -0,0 +1,36 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + +accounts: +- accessKey: RocketMQ + secretKey: 12345678 + whiteRemoteAddress: 192.168.0.* + admin: false + defaultTopicPerm: DENY + defaultGroupPerm: SUB + topicPerms: + - topicA=DENY + - topicB=PUB|SUB + - topicC=SUB + groupPerms: + - groupA=DENY + - groupB=SUB + - groupC=SUB +- accessKey: rocketmq2 + secretKey: 12345678 + whiteRemoteAddress: 192.168.1.* + admin: true diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel new file mode 100644 index 00000000000..a9fd83fea0b --- /dev/null +++ b/bazel/BUILD.bazel @@ -0,0 +1,16 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# \ No newline at end of file diff --git a/bazel/GenTestRules.bzl b/bazel/GenTestRules.bzl new file mode 100644 index 00000000000..73cbb4a84ba --- /dev/null +++ b/bazel/GenTestRules.bzl @@ -0,0 +1,109 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 = [], + 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, + args = args, + flaky = flaky, + tags = tags, + test_class = java_class, + visibility = visibility, + shard_count = shard_count, + ) + +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 diff --git a/broker/BUILD.bazel b/broker/BUILD.bazel new file mode 100644 index 00000000000..d0d3a2f96ba --- /dev/null +++ b/broker/BUILD.bazel @@ -0,0 +1,93 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 = [ + "//acl", + "//client", + "//common", + "//filter", + "//remoting", + "//srvutil", + "//store", + "@maven//:ch_qos_logback_logback_classic", + "@maven//:com_alibaba_fastjson", + "@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_sdk", + "@maven//:io_opentelemetry_opentelemetry_sdk_common", + "@maven//:io_opentelemetry_opentelemetry_sdk_metrics", + "@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_slf4j_jul_to_slf4j", + "@maven//:io_github_aliyunmq_rocketmq_shaded_slf4j_api_bridge", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + resources = [ + "src/test/resources/META-INF/service/org.apache.rocketmq.acl.AccessValidator", + "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", + "//acl", + "//client", + "//common", + "//filter", + "//remoting", + "//store", + "@maven//:com_alibaba_fastjson", + "@maven//:com_google_guava_guava", + "@maven//:io_netty_netty_all", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:io_github_aliyunmq_rocketmq_slf4j_api", + "@maven//:org_powermock_powermock_core", + ], +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], +) diff --git a/broker/pom.xml b/broker/pom.xml index fee6d9ea2c8..70ba0ee665e 100644 --- a/broker/pom.xml +++ b/broker/pom.xml @@ -1,19 +1,19 @@ - org.apache.rocketmq rocketmq-all - 4.9.4-SNAPSHOT + 5.1.3 4.0.0 @@ -21,10 +21,14 @@ rocketmq-broker rocketmq-broker ${project.version} + + ${basedir}/.. + + ${project.groupId} - rocketmq-common + rocketmq-remoting ${project.groupId} @@ -32,7 +36,15 @@ ${project.groupId} - rocketmq-remoting + rocketmq-tiered-store + + + io.github.aliyunmq + rocketmq-slf4j-api + + + io.github.aliyunmq + rocketmq-logback-classic ${project.groupId} @@ -51,8 +63,8 @@ rocketmq-acl - ch.qos.logback - logback-classic + commons-io + commons-io com.alibaba @@ -62,21 +74,29 @@ org.javassist javassist - - org.slf4j - slf4j-api - org.bouncycastle bcpkix-jdk15on + + com.googlecode.concurrentlinkedhashmap + concurrentlinkedhashmap-lru + + + io.github.aliyunmq + rocketmq-shaded-slf4j-api-bridge + + + org.slf4j + jul-to-slf4j + maven-surefire-plugin - 2.19.1 + ${maven-surefire-plugin.version} 1 false diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java index 7bfc618b922..196401e268b 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -16,24 +16,9 @@ */ package org.apache.rocketmq.broker; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.ArrayList; -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.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import com.google.common.collect.Lists; import org.apache.rocketmq.acl.AccessValidator; +import org.apache.rocketmq.acl.plain.PlainAccessValidator; import org.apache.rocketmq.broker.client.ClientHousekeepingService; import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener; import org.apache.rocketmq.broker.client.ConsumerManager; @@ -41,56 +26,75 @@ 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.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.filtersrv.FilterServerManager; import org.apache.rocketmq.broker.latency.BrokerFastFailure; import org.apache.rocketmq.broker.latency.BrokerFixedThreadPoolExecutor; 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.ConsumerOrderInfoManager; import org.apache.rocketmq.broker.offset.LmqConsumerOffsetManager; import org.apache.rocketmq.broker.out.BrokerOuterAPI; -import org.apache.rocketmq.broker.plugin.MessageStoreFactory; -import org.apache.rocketmq.broker.plugin.MessageStorePluginContext; +import org.apache.rocketmq.broker.plugin.BrokerAttachedPlugin; +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.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.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.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.TransactionalMessageCheckService; import org.apache.rocketmq.broker.transaction.TransactionalMessageService; 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.ServiceProvider; +import org.apache.rocketmq.broker.util.HookUtils; +import org.apache.rocketmq.common.AbstractBrokerRunnable; import org.apache.rocketmq.common.BrokerConfig; -import org.apache.rocketmq.common.Configuration; -import org.apache.rocketmq.common.DataVersion; +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.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; -import org.apache.rocketmq.common.namesrv.RegisterBrokerResult; -import org.apache.rocketmq.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper; +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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.common.utils.ServiceProvider; +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; @@ -100,80 +104,190 @@ 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.BrokerSyncInfo; +import org.apache.rocketmq.remoting.protocol.DataVersion; +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.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.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 java.io.IOException; +import java.net.InetSocketAddress; +import java.util.AbstractMap; +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.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.ScheduledThreadPoolExecutor; +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.stream.Collectors; public class BrokerController { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); - private static final InternalLogger LOG_PROTECTION = InternalLoggerFactory.getLogger(LoggerName.PROTECTION_LOGGER_NAME); - private static final InternalLogger LOG_WATER_MARK = InternalLoggerFactory.getLogger(LoggerName.WATER_MARK_LOGGER_NAME); - private final BrokerConfig brokerConfig; + 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; - private final MessageStoreConfig messageStoreConfig; - private final ConsumerOffsetManager consumerOffsetManager; - private final ConsumerManager consumerManager; - private final ConsumerFilterManager consumerFilterManager; - private final ProducerManager producerManager; - private final ClientHousekeepingService clientHousekeepingService; - private final PullMessageProcessor pullMessageProcessor; - private final PullRequestHoldService pullRequestHoldService; - private final MessageArrivingListener messageArrivingListener; - private final Broker2Client broker2Client; - private final SubscriptionGroupManager subscriptionGroupManager; - private final ConsumerIdsChangeListener consumerIdsChangeListener; + protected final MessageStoreConfig messageStoreConfig; + protected final ConsumerOffsetManager consumerOffsetManager; + protected final BroadcastOffsetManager broadcastOffsetManager; + protected final ConsumerManager consumerManager; + protected final ConsumerFilterManager consumerFilterManager; + protected final ConsumerOrderInfoManager consumerOrderInfoManager; + protected final PopInflightMessageCounter popInflightMessageCounter; + 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 AckMessageProcessor ackMessageProcessor; + protected final ChangeInvisibleTimeProcessor changeInvisibleTimeProcessor; + protected final NotificationProcessor notificationProcessor; + protected final PollingInfoProcessor pollingInfoProcessor; + protected final QueryAssignmentProcessor queryAssignmentProcessor; + protected final ClientManageProcessor clientManageProcessor; + protected final SendMessageProcessor sendMessageProcessor; + 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 BrokerOuterAPI brokerOuterAPI; - private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl( - "BrokerControllerScheduledThread")); - private final SlaveSynchronize slaveSynchronize; - private final BlockingQueue sendThreadPoolQueue; - private final BlockingQueue putThreadPoolQueue; - private final BlockingQueue pullThreadPoolQueue; - private final BlockingQueue replyThreadPoolQueue; - private final BlockingQueue queryThreadPoolQueue; - private final BlockingQueue clientManagerThreadPoolQueue; - private final BlockingQueue heartbeatThreadPoolQueue; - private final BlockingQueue consumerManagerThreadPoolQueue; - private final BlockingQueue endTransactionThreadPoolQueue; - private final FilterServerManager filterServerManager; - private final BrokerStatsManager brokerStatsManager; - private final List sendMessageHookList = new ArrayList(); - private final List consumeMessageHookList = new ArrayList(); - private MessageStore messageStore; - private RemotingServer remotingServer; - private RemotingServer fastRemotingServer; - private TopicConfigManager topicConfigManager; - private ExecutorService sendMessageExecutor; - private ExecutorService putMessageFutureExecutor; - private ExecutorService pullMessageExecutor; - private ExecutorService replyMessageExecutor; - private ExecutorService queryMessageExecutor; - private ExecutorService adminBrokerExecutor; - private ExecutorService clientManageExecutor; - private ExecutorService heartbeatExecutor; - private ExecutorService consumerManageExecutor; - private ExecutorService endTransactionExecutor; - private boolean updateMasterHAServerAddrPeriodically = false; + 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 final BrokerStatsManager brokerStatsManager; + protected final List sendMessageHookList = new ArrayList<>(); + protected final List consumeMessageHookList = new ArrayList<>(); + protected MessageStore messageStore; + protected RemotingServer remotingServer; + protected CountDownLatch remotingServerStartLatch; + protected RemotingServer fastRemotingServer; + 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 BrokerFastFailure brokerFastFailure; + private TimerMessageStore timerMessageStore; + private TimerCheckpoint timerCheckpoint; + protected BrokerFastFailure brokerFastFailure; private Configuration configuration; - private FileWatchService fileWatchService; - private TransactionalMessageCheckService transactionalMessageCheckService; - private TransactionalMessageService transactionalMessageService; - private AbstractTransactionalMessageCheckListener transactionalMessageCheckListener; - private Future slaveSyncFuture; - private Map accessValidatorMap = new HashMap(); + protected TopicQueueMappingCleanService topicQueueMappingCleanService; + protected FileWatchService fileWatchService; + protected TransactionalMessageCheckService transactionalMessageCheckService; + protected TransactionalMessageService transactionalMessageService; + protected AbstractTransactionalMessageCheckListener transactionalMessageCheckListener; + protected Map accessValidatorMap = new HashMap<>(); + 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(); + private BrokerMetricsManager brokerMetricsManager; + private ColdDataPullRequestHoldService coldDataPullRequestHoldService; + private ColdDataCgCtrService coldDataCgCtrService; + + public BrokerController( + final BrokerConfig brokerConfig, + final NettyServerConfig nettyServerConfig, + final NettyClientConfig nettyClientConfig, + final MessageStoreConfig messageStoreConfig, + final ShutdownHook shutdownHook + ) { + this(brokerConfig, nettyServerConfig, nettyClientConfig, messageStoreConfig); + this.shutdownHook = shutdownHook; + } + + public BrokerController( + final BrokerConfig brokerConfig, + final MessageStoreConfig messageStoreConfig + ) { + this(brokerConfig, null, null, messageStoreConfig); + } public BrokerController( final BrokerConfig brokerConfig, @@ -185,43 +299,106 @@ public BrokerController( this.nettyServerConfig = nettyServerConfig; this.nettyClientConfig = nettyClientConfig; this.messageStoreConfig = messageStoreConfig; + this.setStoreHost(new InetSocketAddress(this.getBrokerConfig().getBrokerIP1(), getListenPort())); + this.brokerStatsManager = messageStoreConfig.isEnableLmq() ? new LmqBrokerStatsManager(this.brokerConfig.getBrokerClusterName(), this.brokerConfig.isEnableDetailStat()) : new BrokerStatsManager(this.brokerConfig.getBrokerClusterName(), this.brokerConfig.isEnableDetailStat()); this.consumerOffsetManager = messageStoreConfig.isEnableLmq() ? new LmqConsumerOffsetManager(this) : new ConsumerOffsetManager(this); + this.broadcastOffsetManager = new BroadcastOffsetManager(this); this.topicConfigManager = messageStoreConfig.isEnableLmq() ? new LmqTopicConfigManager(this) : new TopicConfigManager(this); + this.topicQueueMappingManager = new TopicQueueMappingManager(this); this.pullMessageProcessor = new PullMessageProcessor(this); + this.peekMessageProcessor = new PeekMessageProcessor(this); this.pullRequestHoldService = messageStoreConfig.isEnableLmq() ? new LmqPullRequestHoldService(this) : new PullRequestHoldService(this); - this.messageArrivingListener = new NotifyMessageArrivingListener(this.pullRequestHoldService); + this.popMessageProcessor = new PopMessageProcessor(this); + 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.replyMessageProcessor = new ReplyMessageProcessor(this); + this.messageArrivingListener = new NotifyMessageArrivingListener(this.pullRequestHoldService, this.popMessageProcessor, this.notificationProcessor); this.consumerIdsChangeListener = new DefaultConsumerIdsChangeListener(this); - this.consumerManager = new ConsumerManager(this.consumerIdsChangeListener); + this.consumerManager = new ConsumerManager(this.consumerIdsChangeListener, this.brokerStatsManager, this.brokerConfig); + this.producerManager = new ProducerManager(this.brokerStatsManager); this.consumerFilterManager = new ConsumerFilterManager(this); - this.producerManager = new ProducerManager(); + this.consumerOrderInfoManager = new ConsumerOrderInfoManager(this); + this.popInflightMessageCounter = new PopInflightMessageCounter(this); this.clientHousekeepingService = new ClientHousekeepingService(this); this.broker2Client = new Broker2Client(this); this.subscriptionGroupManager = messageStoreConfig.isEnableLmq() ? new LmqSubscriptionGroupManager(this) : new SubscriptionGroupManager(this); - this.brokerOuterAPI = new BrokerOuterAPI(nettyClientConfig); - this.filterServerManager = new FilterServerManager(this); - - this.slaveSynchronize = new SlaveSynchronize(this); - - this.sendThreadPoolQueue = new LinkedBlockingQueue(this.brokerConfig.getSendThreadPoolQueueCapacity()); - this.putThreadPoolQueue = new LinkedBlockingQueue(this.brokerConfig.getPutThreadPoolQueueCapacity()); - this.pullThreadPoolQueue = new LinkedBlockingQueue(this.brokerConfig.getPullThreadPoolQueueCapacity()); - 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.scheduleMessageService = new ScheduleMessageService(this); + this.coldDataPullRequestHoldService = new ColdDataPullRequestHoldService(this); + this.coldDataCgCtrService = new ColdDataCgCtrService(this); - this.brokerStatsManager = messageStoreConfig.isEnableLmq() ? new LmqBrokerStatsManager(this.brokerConfig.getBrokerClusterName(), this.brokerConfig.isEnableDetailStat()) : new BrokerStatsManager(this.brokerConfig.getBrokerClusterName(), this.brokerConfig.isEnableDetailStat()); + if (nettyClientConfig != null) { + this.brokerOuterAPI = new BrokerOuterAPI(nettyClientConfig); + } - this.setStoreHost(new InetSocketAddress(this.getBrokerConfig().getBrokerIP1(), this.getNettyServerConfig().getListenPort())); + 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, - BrokerPathConfigHelper.getBrokerConfigPath(), + LOG, + brokerConfigPath, this.brokerConfig, this.nettyServerConfig, this.nettyClientConfig, this.messageStoreConfig ); + + this.brokerStatsManager.setProduerStateGetter(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); + + this.topicRouteInfoManager = new TopicRouteInfoManager(this); + + if (this.brokerConfig.isEnableSlaveActingMaster() && !this.brokerConfig.isSkipPreOnline()) { + this.brokerPreOnlineService = new BrokerPreOnlineService(this); + } } public BrokerConfig getBrokerConfig() { @@ -232,6 +409,10 @@ public NettyServerConfig getNettyServerConfig() { return nettyServerConfig; } + public NettyClientConfig getNettyClientConfig() { + return nettyClientConfig; + } + public BlockingQueue getPullThreadPoolQueue() { return pullThreadPoolQueue; } @@ -240,221 +421,420 @@ public BlockingQueue getQueryThreadPoolQueue() { return queryThreadPoolQueue; } - public boolean initialize() throws CloneNotSupportedException { - boolean result = this.topicConfigManager.load(); + public BrokerMetricsManager getBrokerMetricsManager() { + return brokerMetricsManager; + } - result = result && this.consumerOffsetManager.load(); - result = result && this.subscriptionGroupManager.load(); - result = result && this.consumerFilterManager.load(); + protected void initializeRemotingServer() throws CloneNotSupportedException { + this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.clientHousekeepingService); + NettyServerConfig fastConfig = (NettyServerConfig) this.nettyServerConfig.clone(); - if (result) { - try { - this.messageStore = - new DefaultMessageStore(this.messageStoreConfig, this.brokerStatsManager, this.messageArrivingListener, - this.brokerConfig); - if (messageStoreConfig.isEnableDLegerCommitLog()) { - DLedgerRoleChangeHandler roleChangeHandler = new DLedgerRoleChangeHandler(this, (DefaultMessageStore) messageStore); - ((DLedgerCommitLog)((DefaultMessageStore) messageStore).getCommitLog()).getdLedgerServer().getdLedgerLeaderElector().addRoleChangeHandler(roleChangeHandler); + int listeningPort = nettyServerConfig.getListenPort() - 2; + if (listeningPort < 0) { + listeningPort = 0; + } + fastConfig.setListenPort(listeningPort); + + this.fastRemotingServer = new NettyRemotingServer(fastConfig, this.clientHousekeepingService); + } + + /** + * Initialize resources including remoting server and thread executors. + */ + protected void initializeResources() { + this.scheduledExecutorService = new ScheduledThreadPoolExecutor(1, + new ThreadFactoryImpl("BrokerControllerScheduledThread", true, getBrokerIdentity())); + + this.sendMessageExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getSendMessageThreadPoolNums(), + this.brokerConfig.getSendMessageThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.sendThreadPoolQueue, + new ThreadFactoryImpl("SendMessageThread_", getBrokerIdentity())); + + this.pullMessageExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getPullMessageThreadPoolNums(), + this.brokerConfig.getPullMessageThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.pullThreadPoolQueue, + new ThreadFactoryImpl("PullMessageThread_", getBrokerIdentity())); + + this.litePullMessageExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getLitePullMessageThreadPoolNums(), + this.brokerConfig.getLitePullMessageThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.litePullThreadPoolQueue, + new ThreadFactoryImpl("LitePullMessageThread_", getBrokerIdentity())); + + this.putMessageFutureExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getPutMessageFutureThreadPoolNums(), + this.brokerConfig.getPutMessageFutureThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.putThreadPoolQueue, + new ThreadFactoryImpl("SendMessageThread_", getBrokerIdentity())); + + this.ackMessageExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getAckMessageThreadPoolNums(), + this.brokerConfig.getAckMessageThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.ackThreadPoolQueue, + new ThreadFactoryImpl("AckMessageThread_", getBrokerIdentity())); + + this.queryMessageExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getQueryMessageThreadPoolNums(), + this.brokerConfig.getQueryMessageThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.queryThreadPoolQueue, + new ThreadFactoryImpl("QueryMessageThread_", getBrokerIdentity())); + + this.adminBrokerExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getAdminBrokerThreadPoolNums(), + this.brokerConfig.getAdminBrokerThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.adminBrokerThreadPoolQueue, + new ThreadFactoryImpl("AdminBrokerThread_", getBrokerIdentity())); + + this.clientManageExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getClientManageThreadPoolNums(), + this.brokerConfig.getClientManageThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.clientManagerThreadPoolQueue, + new ThreadFactoryImpl("ClientManageThread_", getBrokerIdentity())); + + this.heartbeatExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getHeartbeatThreadPoolNums(), + this.brokerConfig.getHeartbeatThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.heartbeatThreadPoolQueue, + new ThreadFactoryImpl("HeartbeatThread_", true, getBrokerIdentity())); + + this.consumerManageExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getConsumerManageThreadPoolNums(), + this.brokerConfig.getConsumerManageThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.consumerManagerThreadPoolQueue, + new ThreadFactoryImpl("ConsumerManageThread_", true, getBrokerIdentity())); + + this.replyMessageExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getProcessReplyMessageThreadPoolNums(), + this.brokerConfig.getProcessReplyMessageThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.replyThreadPoolQueue, + new ThreadFactoryImpl("ProcessReplyMessageThread_", getBrokerIdentity())); + + this.endTransactionExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getEndTransactionThreadPoolNums(), + this.brokerConfig.getEndTransactionThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.endTransactionThreadPoolQueue, + new ThreadFactoryImpl("EndTransactionThread_", getBrokerIdentity())); + + this.loadBalanceExecutor = new BrokerFixedThreadPoolExecutor( + this.brokerConfig.getLoadBalanceProcessorThreadPoolNums(), + this.brokerConfig.getLoadBalanceProcessorThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.loadBalanceThreadPoolQueue, + new ThreadFactoryImpl("LoadBalanceProcessorThread_", getBrokerIdentity())); + + this.syncBrokerMemberGroupExecutorService = new ScheduledThreadPoolExecutor(1, + new ThreadFactoryImpl("BrokerControllerSyncBrokerScheduledThread", getBrokerIdentity())); + this.brokerHeartbeatExecutorService = new ScheduledThreadPoolExecutor(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); } - this.brokerStats = new BrokerStats((DefaultMessageStore) this.messageStore); - //load plugin - MessageStorePluginContext context = new MessageStorePluginContext(messageStoreConfig, brokerStatsManager, messageArrivingListener, brokerConfig); - this.messageStore = MessageStoreFactory.build(context, this.messageStore); - this.messageStore.getDispatcherList().addFirst(new CommitLogDispatcherCalcBitMap(this.brokerConfig, this.consumerFilterManager)); - } catch (IOException e) { - result = false; - log.error("Failed to initialize", e); } - } + }, initialDelay, period, TimeUnit.MILLISECONDS); - result = result && this.messageStore.load(); + 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); - if (result) { - this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.clientHousekeepingService); - NettyServerConfig fastConfig = (NettyServerConfig) this.nettyServerConfig.clone(); - fastConfig.setListenPort(nettyServerConfig.getListenPort() - 2); - this.fastRemotingServer = new NettyRemotingServer(fastConfig, this.clientHousekeepingService); - this.sendMessageExecutor = new BrokerFixedThreadPoolExecutor( - this.brokerConfig.getSendMessageThreadPoolNums(), - this.brokerConfig.getSendMessageThreadPoolNums(), - 1000 * 60, - TimeUnit.MILLISECONDS, - this.sendThreadPoolQueue, - new ThreadFactoryImpl("SendMessageThread_")); - - this.putMessageFutureExecutor = new BrokerFixedThreadPoolExecutor( - this.brokerConfig.getPutMessageFutureThreadPoolNums(), - this.brokerConfig.getPutMessageFutureThreadPoolNums(), - 1000 * 60, - TimeUnit.MILLISECONDS, - this.putThreadPoolQueue, - new ThreadFactoryImpl("PutMessageThread_")); - - this.pullMessageExecutor = new BrokerFixedThreadPoolExecutor( - this.brokerConfig.getPullMessageThreadPoolNums(), - this.brokerConfig.getPullMessageThreadPoolNums(), - 1000 * 60, - TimeUnit.MILLISECONDS, - this.pullThreadPoolQueue, - new ThreadFactoryImpl("PullMessageThread_")); - - this.replyMessageExecutor = new BrokerFixedThreadPoolExecutor( - this.brokerConfig.getProcessReplyMessageThreadPoolNums(), - this.brokerConfig.getProcessReplyMessageThreadPoolNums(), - 1000 * 60, - TimeUnit.MILLISECONDS, - this.replyThreadPoolQueue, - new ThreadFactoryImpl("ProcessReplyMessageThread_")); - - this.queryMessageExecutor = new BrokerFixedThreadPoolExecutor( - this.brokerConfig.getQueryMessageThreadPoolNums(), - this.brokerConfig.getQueryMessageThreadPoolNums(), - 1000 * 60, - TimeUnit.MILLISECONDS, - this.queryThreadPoolQueue, - new ThreadFactoryImpl("QueryMessageThread_")); - - this.adminBrokerExecutor = - Executors.newFixedThreadPool(this.brokerConfig.getAdminBrokerThreadPoolNums(), new ThreadFactoryImpl( - "AdminBrokerThread_")); - - this.clientManageExecutor = new ThreadPoolExecutor( - this.brokerConfig.getClientManageThreadPoolNums(), - this.brokerConfig.getClientManageThreadPoolNums(), - 1000 * 60, - TimeUnit.MILLISECONDS, - this.clientManagerThreadPoolQueue, - new ThreadFactoryImpl("ClientManageThread_")); - - this.heartbeatExecutor = new BrokerFixedThreadPoolExecutor( - this.brokerConfig.getHeartbeatThreadPoolNums(), - this.brokerConfig.getHeartbeatThreadPoolNums(), - 1000 * 60, - TimeUnit.MILLISECONDS, - this.heartbeatThreadPoolQueue, - new ThreadFactoryImpl("HeartbeatThread_", true)); - - this.endTransactionExecutor = new BrokerFixedThreadPoolExecutor( - this.brokerConfig.getEndTransactionThreadPoolNums(), - this.brokerConfig.getEndTransactionThreadPoolNums(), - 1000 * 60, - TimeUnit.MILLISECONDS, - this.endTransactionThreadPoolQueue, - new ThreadFactoryImpl("EndTransactionThread_")); - - this.consumerManageExecutor = - Executors.newFixedThreadPool(this.brokerConfig.getConsumerManageThreadPoolNums(), new ThreadFactoryImpl( - "ConsumerManageThread_")); - - this.registerProcessor(); - - final long initialDelay = UtilAll.computeNextMorningTimeMillis() - System.currentTimeMillis(); - final long period = 1000 * 60 * 60 * 24; - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - BrokerController.this.getBrokerStats().record(); - } catch (Throwable e) { - log.error("schedule record error.", e); - } + 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); } - }, initialDelay, period, TimeUnit.MILLISECONDS); + } + }, 1000 * 10, 1000 * 10, TimeUnit.MILLISECONDS); - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - BrokerController.this.consumerOffsetManager.persist(); - } catch (Throwable e) { - log.error("schedule persist consumerOffset error.", e); - } + this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + try { + BrokerController.this.protectBroker(); + } catch (Throwable e) { + LOG.error("BrokerController: failed to protectBroker", e); } - }, 1000 * 10, this.brokerConfig.getFlushConsumerOffsetInterval(), TimeUnit.MILLISECONDS); + } + }, 3, 3, TimeUnit.MINUTES); - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - BrokerController.this.consumerFilterManager.persist(); - } catch (Throwable e) { - log.error("schedule persist consumer filter error.", e); - } + 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); } - }, 1000 * 10, 1000 * 10, TimeUnit.MILLISECONDS); + } + }, 10, 1, TimeUnit.SECONDS); - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - BrokerController.this.protectBroker(); - } catch (Throwable e) { - log.error("protectBroker error.", e); + 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(); + + this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + try { + BrokerController.this.brokerOuterAPI.refreshMetadata(); + } catch (Exception e) { + LOG.error("ScheduledTask refresh metadata exception", e); } - }, 3, 3, TimeUnit.MINUTES); + } + }, 10, 5, TimeUnit.SECONDS); + 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.printWaterMark(); + BrokerController.this.updateNamesrvAddr(); } catch (Throwable e) { - log.error("printWaterMark error.", e); + LOG.error("Failed to update nameServer address list", e); } } - }, 10, 1, TimeUnit.SECONDS); - + }, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS); + } else if (this.brokerConfig.isFetchNamesrvAddrByAddressServer()) { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { - log.info("dispatch behind commit log {} bytes", BrokerController.this.getMessageStore().dispatchBehindBytes()); + BrokerController.this.brokerOuterAPI.fetchNameServerAddr(); } catch (Throwable e) { - log.error("schedule dispatchBehindBytes error.", e); + LOG.error("Failed to fetch nameServer address", e); } } - }, 1000 * 10, 1000 * 60, TimeUnit.MILLISECONDS); + }, 1000 * 10, this.brokerConfig.getFetchNamesrvAddrInterval(), TimeUnit.MILLISECONDS); + } + } - if (this.brokerConfig.getNamesrvAddr() != null) { - this.brokerOuterAPI.updateNameServerAddressList(this.brokerConfig.getNamesrvAddr()); - log.info("Set user specified name server address: {}", this.brokerConfig.getNamesrvAddr()); - } else if (this.brokerConfig.isFetchNamesrvAddrByAddressServer()) { - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + private void updateNamesrvAddr() { + if (this.brokerConfig.isFetchNameSrvAddrByDnsLookup()) { + this.brokerOuterAPI.updateNameServerAddressListByDnsLookup(this.brokerConfig.getNamesrvAddr()); + } else { + this.brokerOuterAPI.updateNameServerAddressList(this.brokerConfig.getNamesrvAddr()); + } + } - @Override - public void run() { - try { - BrokerController.this.brokerOuterAPI.fetchNameServerAddr(); - } catch (Throwable e) { - log.error("ScheduledTask fetchNameServerAddr exception", e); - } - } - }, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS); + public boolean initializeMetadata() { + boolean 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 = 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); } - if (!messageStoreConfig.isEnableDLegerCommitLog()) { - if (BrokerRole.SLAVE == this.messageStoreConfig.getBrokerRole()) { - if (this.messageStoreConfig.getHaMasterAddress() != null && this.messageStoreConfig.getHaMasterAddress().length() >= 6) { - this.messageStore.updateHaMasterAddress(this.messageStoreConfig.getHaMasterAddress()); - this.updateMasterHAServerAddrPeriodically = false; - } else { - this.updateMasterHAServerAddrPeriodically = true; - } - } else { - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - BrokerController.this.printMasterAndSlaveDiff(); - } catch (Throwable e) { - log.error("schedule printMasterAndSlaveDiff error.", e); - } - } - }, 1000 * 10, 1000 * 60, TimeUnit.MILLISECONDS); - } + 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); + } + } catch (IOException 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(); + } + + //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(); + + initialAcl(); + + initialRpcHooks(); if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) { // Register a listener to reload SslContext @@ -471,7 +851,7 @@ public void run() { @Override public void onChanged(String path) { if (path.equals(TlsSystemConfig.tlsServerTrustCertPath)) { - log.info("The trust certificate changed, reload the ssl context"); + LOG.info("The trust certificate changed, reload the ssl context"); reloadServerSslContext(); } if (path.equals(TlsSystemConfig.tlsServerCertPath)) { @@ -481,7 +861,7 @@ public void onChanged(String path) { keyChanged = true; } if (certChanged && keyChanged) { - log.info("The certificate and private key changed, reload the ssl context"); + LOG.info("The certificate and private key changed, reload the ssl context"); certChanged = keyChanged = false; reloadServerSslContext(); } @@ -493,26 +873,86 @@ private void reloadServerSslContext() { } }); } catch (Exception e) { - log.warn("FileWatchService created error, can't load the certificate dynamically"); + result = false; + LOG.warn("FileWatchService created error, can't load the certificate dynamically"); } } - initialTransaction(); - initialAcl(); - initialRpcHooks(); } + 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; + } + }); + + 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(ServiceProvider.TRANSACTION_SERVICE_ID, TransactionalMessageService.class); + 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.transactionalMessageService = new TransactionalMessageServiceImpl( + new TransactionalMessageBridge(this, this.getMessageStore())); + LOG.warn("Load default transaction message hook service: {}", + TransactionalMessageServiceImpl.class.getSimpleName()); } - this.transactionalMessageCheckListener = ServiceProvider.loadClass(ServiceProvider.TRANSACTION_LISTENER_ID, AbstractTransactionalMessageCheckListener.class); + 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()); + LOG.warn("Load default discard message hook service: {}", + DefaultTransactionalMessageCheckListener.class.getSimpleName()); } this.transactionalMessageCheckListener.setBrokerController(this); this.transactionalMessageCheckService = new TransactionalMessageCheckService(this); @@ -520,19 +960,19 @@ private void initialTransaction() { private void initialAcl() { if (!this.brokerConfig.isAclEnable()) { - log.info("The broker dose not enable acl"); + LOG.info("The broker dose not enable acl"); return; } - List accessValidators = ServiceProvider.load(ServiceProvider.ACL_VALIDATOR_ID, AccessValidator.class); - if (accessValidators == null || accessValidators.isEmpty()) { - log.info("The broker dose not load the AccessValidator"); - return; + List accessValidators = ServiceProvider.load(AccessValidator.class); + if (accessValidators.isEmpty()) { + LOG.info("ServiceProvider loaded no AccessValidator, using default org.apache.rocketmq.acl.plain.PlainAccessValidator"); + accessValidators.add(new PlainAccessValidator()); } - for (AccessValidator accessValidator: accessValidators) { + for (AccessValidator accessValidator : accessValidators) { final AccessValidator validator = accessValidator; - accessValidatorMap.put(validator.getClass(),validator); + accessValidatorMap.put(validator.getClass(), validator); this.registerServerRPCHook(new RPCHook() { @Override @@ -544,48 +984,79 @@ public void doBeforeRequest(String remoteAddr, RemotingCommand request) { @Override public void doAfterResponse(String remoteAddr, RemotingCommand request, RemotingCommand response) { } + }); } } - private void initialRpcHooks() { - List rpcHooks = ServiceProvider.load(ServiceProvider.RPC_HOOK_ID, RPCHook.class); + List rpcHooks = ServiceProvider.load(RPCHook.class); if (rpcHooks == null || rpcHooks.isEmpty()) { return; } - for (RPCHook rpcHook: rpcHooks) { + for (RPCHook rpcHook : rpcHooks) { this.registerServerRPCHook(rpcHook); } } public void registerProcessor() { - /** + /* * SendMessageProcessor */ - SendMessageProcessor sendProcessor = new SendMessageProcessor(this); - sendProcessor.registerSendMessageHook(sendMessageHookList); - sendProcessor.registerConsumeMessageHook(consumeMessageHookList); - - this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, this.sendMessageExecutor); - this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendProcessor, this.sendMessageExecutor); - this.remotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendProcessor, this.sendMessageExecutor); - this.remotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendProcessor, this.sendMessageExecutor); - this.fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, this.sendMessageExecutor); - this.fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendProcessor, this.sendMessageExecutor); - this.fastRemotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendProcessor, this.sendMessageExecutor); - this.fastRemotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendProcessor, this.sendMessageExecutor); + sendMessageProcessor.registerSendMessageHook(sendMessageHookList); + sendMessageProcessor.registerConsumeMessageHook(consumeMessageHookList); + + this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendMessageProcessor, this.sendMessageExecutor); + this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendMessageProcessor, this.sendMessageExecutor); + this.remotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendMessageProcessor, this.sendMessageExecutor); + this.remotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendMessageProcessor, this.sendMessageExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendMessageProcessor, this.sendMessageExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendMessageProcessor, this.sendMessageExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendMessageProcessor, this.sendMessageExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendMessageProcessor, this.sendMessageExecutor); /** * PullMessageProcessor */ this.remotingServer.registerProcessor(RequestCode.PULL_MESSAGE, this.pullMessageProcessor, this.pullMessageExecutor); + this.remotingServer.registerProcessor(RequestCode.LITE_PULL_MESSAGE, this.pullMessageProcessor, this.litePullMessageExecutor); this.pullMessageProcessor.registerConsumeMessageHook(consumeMessageHookList); + /** + * PeekMessageProcessor + */ + this.remotingServer.registerProcessor(RequestCode.PEEK_MESSAGE, this.peekMessageProcessor, this.pullMessageExecutor); + /** + * PopMessageProcessor + */ + this.remotingServer.registerProcessor(RequestCode.POP_MESSAGE, this.popMessageProcessor, this.pullMessageExecutor); + + /** + * AckMessageProcessor + */ + this.remotingServer.registerProcessor(RequestCode.ACK_MESSAGE, this.ackMessageProcessor, this.ackMessageExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.ACK_MESSAGE, this.ackMessageProcessor, this.ackMessageExecutor); + + this.remotingServer.registerProcessor(RequestCode.BATCH_ACK_MESSAGE, this.ackMessageProcessor, this.ackMessageExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.BATCH_ACK_MESSAGE, this.ackMessageProcessor, this.ackMessageExecutor); + /** + * ChangeInvisibleTimeProcessor + */ + this.remotingServer.registerProcessor(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, this.changeInvisibleTimeProcessor, this.ackMessageExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, this.changeInvisibleTimeProcessor, this.ackMessageExecutor); + /** + * notificationProcessor + */ + this.remotingServer.registerProcessor(RequestCode.NOTIFICATION, this.notificationProcessor, this.pullMessageExecutor); + + /** + * pollingInfoProcessor + */ + this.remotingServer.registerProcessor(RequestCode.POLLING_INFO, this.pollingInfoProcessor, this.pullMessageExecutor); /** * ReplyMessageProcessor */ - ReplyMessageProcessor replyMessageProcessor = new ReplyMessageProcessor(this); + replyMessageProcessor.registerSendMessageHook(sendMessageHookList); this.remotingServer.registerProcessor(RequestCode.SEND_REPLY_MESSAGE, replyMessageProcessor, replyMessageExecutor); @@ -606,14 +1077,13 @@ public void registerProcessor() { /** * ClientManageProcessor */ - ClientManageProcessor clientProcessor = new ClientManageProcessor(this); - this.remotingServer.registerProcessor(RequestCode.HEART_BEAT, clientProcessor, this.heartbeatExecutor); - this.remotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT, clientProcessor, this.clientManageExecutor); - this.remotingServer.registerProcessor(RequestCode.CHECK_CLIENT_CONFIG, clientProcessor, this.clientManageExecutor); + this.remotingServer.registerProcessor(RequestCode.HEART_BEAT, clientManageProcessor, this.heartbeatExecutor); + this.remotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT, clientManageProcessor, this.clientManageExecutor); + this.remotingServer.registerProcessor(RequestCode.CHECK_CLIENT_CONFIG, clientManageProcessor, this.clientManageExecutor); - this.fastRemotingServer.registerProcessor(RequestCode.HEART_BEAT, clientProcessor, this.heartbeatExecutor); - this.fastRemotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT, clientProcessor, this.clientManageExecutor); - this.fastRemotingServer.registerProcessor(RequestCode.CHECK_CLIENT_CONFIG, clientProcessor, this.clientManageExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.HEART_BEAT, clientManageProcessor, this.heartbeatExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT, clientManageProcessor, this.clientManageExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.CHECK_CLIENT_CONFIG, clientManageProcessor, this.clientManageExecutor); /** * ConsumerManageProcessor @@ -628,12 +1098,20 @@ public void registerProcessor() { this.fastRemotingServer.registerProcessor(RequestCode.QUERY_CONSUMER_OFFSET, consumerManageProcessor, this.consumerManageExecutor); /** - * EndTransactionProcessor + * QueryAssignmentProcessor */ - this.remotingServer.registerProcessor(RequestCode.END_TRANSACTION, new EndTransactionProcessor(this), this.endTransactionExecutor); - this.fastRemotingServer.registerProcessor(RequestCode.END_TRANSACTION, new EndTransactionProcessor(this), this.endTransactionExecutor); + this.remotingServer.registerProcessor(RequestCode.QUERY_ASSIGNMENT, queryAssignmentProcessor, loadBalanceExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.QUERY_ASSIGNMENT, queryAssignmentProcessor, loadBalanceExecutor); + this.remotingServer.registerProcessor(RequestCode.SET_MESSAGE_REQUEST_MODE, queryAssignmentProcessor, loadBalanceExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.SET_MESSAGE_REQUEST_MODE, queryAssignmentProcessor, loadBalanceExecutor); /** + * EndTransactionProcessor + */ + this.remotingServer.registerProcessor(RequestCode.END_TRANSACTION, endTransactionProcessor, this.endTransactionExecutor); + this.fastRemotingServer.registerProcessor(RequestCode.END_TRANSACTION, endTransactionProcessor, this.endTransactionExecutor); + + /* * Default */ AdminBrokerProcessor adminProcessor = new AdminBrokerProcessor(this); @@ -651,9 +1129,7 @@ public void setBrokerStats(BrokerStats brokerStats) { public void protectBroker() { if (this.brokerConfig.isDisableConsumeIfConsumerReadSlowly()) { - final Iterator> it = this.brokerStatsManager.getMomentStatsItemSetFallSize().getStatsItemTable().entrySet().iterator(); - while (it.hasNext()) { - final Map.Entry next = it.next(); + 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("@"); @@ -688,19 +1164,23 @@ public long headSlowTimeMills4PullThreadPoolQueue() { return this.headSlowTimeMills(this.pullThreadPoolQueue); } - public long headSlowTimeMills4QueryThreadPoolQueue() { - return this.headSlowTimeMills(this.queryThreadPoolQueue); + public long headSlowTimeMills4LitePullThreadPoolQueue() { + return this.headSlowTimeMills(this.litePullThreadPoolQueue); } - public long headSlowTimeMills4EndTransactionThreadPoolQueue() { - return this.headSlowTimeMills(this.endTransactionThreadPoolQueue); + public long headSlowTimeMills4QueryThreadPoolQueue() { + return this.headSlowTimeMills(this.queryThreadPoolQueue); } public void printWaterMark() { LOG_WATER_MARK.info("[WATERMARK] Send Queue Size: {} SlowTimeMills: {}", this.sendThreadPoolQueue.size(), headSlowTimeMills4SendThreadPoolQueue()); LOG_WATER_MARK.info("[WATERMARK] Pull Queue Size: {} SlowTimeMills: {}", this.pullThreadPoolQueue.size(), headSlowTimeMills4PullThreadPoolQueue()); LOG_WATER_MARK.info("[WATERMARK] Query Queue Size: {} SlowTimeMills: {}", this.queryThreadPoolQueue.size(), headSlowTimeMills4QueryThreadPoolQueue()); - LOG_WATER_MARK.info("[WATERMARK] Transaction Queue Size: {} SlowTimeMills: {}", this.endTransactionThreadPoolQueue.size(), headSlowTimeMills4EndTransactionThreadPoolQueue()); + LOG_WATER_MARK.info("[WATERMARK] Lite Pull Queue Size: {} SlowTimeMills: {}", this.litePullThreadPoolQueue.size(), headSlowTimeMills4LitePullThreadPoolQueue()); + LOG_WATER_MARK.info("[WATERMARK] Transaction Queue Size: {} SlowTimeMills: {}", this.endTransactionThreadPoolQueue.size(), headSlowTimeMills(this.endTransactionThreadPoolQueue)); + LOG_WATER_MARK.info("[WATERMARK] ClientManager Queue Size: {} SlowTimeMills: {}", this.clientManagerThreadPoolQueue.size(), this.headSlowTimeMills(this.clientManagerThreadPoolQueue)); + LOG_WATER_MARK.info("[WATERMARK] Heartbeat Queue Size: {} SlowTimeMills: {}", this.heartbeatThreadPoolQueue.size(), this.headSlowTimeMills(this.heartbeatThreadPoolQueue)); + LOG_WATER_MARK.info("[WATERMARK] Ack Queue Size: {} SlowTimeMills: {}", this.ackThreadPoolQueue.size(), headSlowTimeMills(this.ackThreadPoolQueue)); } public MessageStore getMessageStore() { @@ -711,11 +1191,11 @@ public void setMessageStore(MessageStore messageStore) { this.messageStore = messageStore; } - private void printMasterAndSlaveDiff() { - long diff = this.messageStore.slaveFallBehindMuch(); - - // XXX: warn and notify me - log.info("Slave fall behind master: {} bytes", diff); + 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() { @@ -730,10 +1210,22 @@ public ConsumerFilterManager getConsumerFilterManager() { return consumerFilterManager; } + public ConsumerOrderInfoManager getConsumerOrderInfoManager() { + return consumerOrderInfoManager; + } + + public PopInflightMessageCounter getPopInflightMessageCounter() { + return popInflightMessageCounter; + } + public ConsumerOffsetManager getConsumerOffsetManager() { return consumerOffsetManager; } + public BroadcastOffsetManager getBroadcastOffsetManager() { + return broadcastOffsetManager; + } + public MessageStoreConfig getMessageStoreConfig() { return messageStoreConfig; } @@ -746,6 +1238,10 @@ public void setFastRemotingServer(RemotingServer fastRemotingServer) { this.fastRemotingServer = fastRemotingServer; } + public RemotingServer getFastRemotingServer() { + return fastRemotingServer; + } + public PullMessageProcessor getPullMessageProcessor() { return pullMessageProcessor; } @@ -754,11 +1250,56 @@ public PullRequestHoldService getPullRequestHoldService() { return pullRequestHoldService; } + public void setSubscriptionGroupManager(SubscriptionGroupManager subscriptionGroupManager) { + this.subscriptionGroupManager = subscriptionGroupManager; + } + public SubscriptionGroupManager getSubscriptionGroupManager() { return subscriptionGroupManager; } - public void shutdown() { + public PopMessageProcessor getPopMessageProcessor() { + return popMessageProcessor; + } + + public NotificationProcessor getNotificationProcessor() { + return notificationProcessor; + } + + public TimerMessageStore getTimerMessageStore() { + return timerMessageStore; + } + + public void setTimerMessageStore(TimerMessageStore timerMessageStore) { + this.timerMessageStore = timerMessageStore; + } + + public AckMessageProcessor getAckMessageProcessor() { + return ackMessageProcessor; + } + + public ChangeInvisibleTimeProcessor getChangeInvisibleTimeProcessor() { + return changeInvisibleTimeProcessor; + } + + protected void shutdownBasicService() { + + shutdown = true; + + this.unregisterBrokerAll(); + + if (this.shutdownHook != null) { + this.shutdownHook.beforeShutdown(this); + } + + if (this.remotingServer != null) { + this.remotingServer.shutdown(); + } + + if (this.fastRemotingServer != null) { + this.fastRemotingServer.shutdown(); + } + if (this.brokerStatsManager != null) { this.brokerStatsManager.shutdown(); } @@ -771,36 +1312,55 @@ public void shutdown() { this.pullRequestHoldService.shutdown(); } - if (this.remotingServer != null) { - this.remotingServer.shutdown(); + { + this.popMessageProcessor.getPopLongPollingService().shutdown(); + this.popMessageProcessor.getQueueLockManager().shutdown(); } - if (this.fastRemotingServer != null) { - this.fastRemotingServer.shutdown(); + { + this.popMessageProcessor.getPopBufferMergeService().shutdown(); + this.ackMessageProcessor.shutdownPopReviveService(); } + 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.fileWatchService != null) { this.fileWatchService.shutdown(); } + if (this.broadcastOffsetManager != null) { + this.broadcastOffsetManager.shutdown(); + } + if (this.messageStore != null) { this.messageStore.shutdown(); } - this.scheduledExecutorService.shutdown(); - try { - this.scheduledExecutorService.awaitTermination(5000, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { + if (this.replicasManager != null) { + this.replicasManager.shutdown(); } - this.unregisterBrokerAll(); + shutdownScheduledExecutorService(this.scheduledExecutorService); if (this.sendMessageExecutor != null) { this.sendMessageExecutor.shutdown(); } - if (this.putMessageFutureExecutor != null) { - this.putMessageFutureExecutor.shutdown(); + if (this.litePullMessageExecutor != null) { + this.litePullMessageExecutor.shutdown(); } if (this.pullMessageExecutor != null) { @@ -811,20 +1371,20 @@ public void shutdown() { this.replyMessageExecutor.shutdown(); } - if (this.adminBrokerExecutor != null) { - this.adminBrokerExecutor.shutdown(); + if (this.putMessageFutureExecutor != null) { + this.putMessageFutureExecutor.shutdown(); } - if (this.brokerOuterAPI != null) { - this.brokerOuterAPI.shutdown(); + if (this.ackMessageExecutor != null) { + this.ackMessageExecutor.shutdown(); } - this.consumerOffsetManager.persist(); - - if (this.filterServerManager != null) { - this.filterServerManager.shutdown(); + if (this.adminBrokerExecutor != null) { + this.adminBrokerExecutor.shutdown(); } + this.consumerOffsetManager.persist(); + if (this.brokerFastFailure != null) { this.brokerFastFailure.shutdown(); } @@ -833,6 +1393,15 @@ public void shutdown() { this.consumerFilterManager.persist(); } + if (this.consumerOrderInfoManager != null) { + this.consumerOrderInfoManager.persist(); + } + + if (this.scheduleMessageService != null) { + this.scheduleMessageService.persist(); + this.scheduleMessageService.shutdown(); + } + if (this.clientManageExecutor != null) { this.clientManageExecutor.shutdown(); } @@ -841,13 +1410,14 @@ public void shutdown() { this.queryMessageExecutor.shutdown(); } + if (this.heartbeatExecutor != null) { + this.heartbeatExecutor.shutdown(); + } + if (this.consumerManageExecutor != null) { this.consumerManageExecutor.shutdown(); } - if (this.fileWatchService != null) { - this.fileWatchService.shutdown(); - } if (this.transactionalMessageCheckService != null) { this.transactionalMessageCheckService.shutdown(false); } @@ -855,9 +1425,67 @@ public void shutdown() { if (this.endTransactionExecutor != null) { this.endTransactionExecutor.shutdown(); } + + if (this.escapeBridge != null) { + 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(); + } + + shutdownScheduledExecutorService(this.syncBrokerMemberGroupExecutorService); + shutdownScheduledExecutorService(this.brokerHeartbeatExecutorService); + + this.topicConfigManager.persist(); + this.subscriptionGroupManager.persist(); + + for (BrokerAttachedPlugin brokerAttachedPlugin : brokerAttachedPlugins) { + if (brokerAttachedPlugin != null) { + brokerAttachedPlugin.shutdown(); + } + } + } + + public void shutdown() { + + shutdownBasicService(); + + for (ScheduledFuture scheduledFuture : scheduledFutures) { + scheduledFuture.cancel(true); + } + + if (this.brokerOuterAPI != null) { + this.brokerOuterAPI.shutdown(); + } } - private void unregisterBrokerAll() { + 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(), @@ -869,96 +1497,249 @@ public String getBrokerAddr() { return this.brokerConfig.getBrokerIP1() + ":" + this.nettyServerConfig.getListenPort(); } - public void start() throws Exception { + protected void startBasicService() throws Exception { + if (this.messageStore != null) { this.messageStore.start(); } - if (this.remotingServer != null) { - this.remotingServer.start(); + if (this.timerMessageStore != null) { + this.timerMessageStore.start(); + } + + if (this.replicasManager != null) { + this.replicasManager.start(); + } + + if (remotingServerStartLatch != null) { + remotingServerStartLatch.await(); + } + + if (this.remotingServer != null) { + 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 (null != nettyServerConfig && 0 == nettyServerConfig.getListenPort()) { + nettyServerConfig.setListenPort(remotingServer.localListenPort()); + } + } + + if (this.fastRemotingServer != null) { + this.fastRemotingServer.start(); + } + + 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(); + this.popMessageProcessor.getPopBufferMergeService().start(); + this.popMessageProcessor.getQueueLockManager().start(); + } + + if (this.ackMessageProcessor != null) { + this.ackMessageProcessor.startPopReviveService(); + } + + if (this.notificationProcessor != null) { + this.notificationProcessor.getPopLongPollingService().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.fastRemotingServer != null) { - this.fastRemotingServer.start(); + if (this.escapeBridge != null) { + this.escapeBridge.start(); } - if (this.fileWatchService != null) { - this.fileWatchService.start(); + if (this.topicRouteInfoManager != null) { + this.topicRouteInfoManager.start(); } - if (this.brokerOuterAPI != null) { - this.brokerOuterAPI.start(); + if (this.brokerPreOnlineService != null) { + this.brokerPreOnlineService.start(); } - if (this.pullRequestHoldService != null) { - this.pullRequestHoldService.start(); + if (this.coldDataPullRequestHoldService != null) { + this.coldDataPullRequestHoldService.start(); } - if (this.clientHousekeepingService != null) { - this.clientHousekeepingService.start(); + if (this.coldDataCgCtrService != null) { + this.coldDataCgCtrService.start(); + } + } + + public void start() throws Exception { + + this.shouldStartTime = System.currentTimeMillis() + messageStoreConfig.getDisappearTimeAfterStart(); + + if (messageStoreConfig.getTotalReplicas() > 1 && this.brokerConfig.isEnableSlaveActingMaster()) { + isIsolated = true; } - if (this.filterServerManager != null) { - this.filterServerManager.start(); + if (this.brokerOuterAPI != null) { + this.brokerOuterAPI.start(); } - if (!messageStoreConfig.isEnableDLegerCommitLog()) { - startProcessorByHa(messageStoreConfig.getBrokerRole()); - handleSlaveSynchronize(messageStoreConfig.getBrokerRole()); + startBasicService(); + + if (!isIsolated && !this.messageStoreConfig.isEnableDLegerCommitLog() && !this.messageStoreConfig.isDuplicationEnable()) { + changeSpecialServiceStatus(this.brokerConfig.getBrokerId() == MixAll.MASTER_ID); this.registerBrokerAll(true, false, true); } - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - + scheduledFutures.add(this.scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) { @Override - public void run() { + public void run0() { 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) { - log.error("registerBrokerAll Exception", e); + BrokerController.LOG.error("registerBrokerAll Exception", e); } } - }, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS); + }, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS)); - if (this.brokerStatsManager != null) { - this.brokerStatsManager.start(); + if (this.brokerConfig.isEnableSlaveActingMaster()) { + scheduleSendHeartbeat(); + + scheduledFutures.add(this.syncBrokerMemberGroupExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) { + @Override + public void run0() { + try { + BrokerController.this.syncBrokerMemberGroup(); + } catch (Throwable e) { + BrokerController.LOG.error("sync BrokerMemberGroup error. ", e); + } + } + }, 1000, this.brokerConfig.getSyncBrokerMemberGroupPeriod(), TimeUnit.MILLISECONDS)); } - if (this.brokerFastFailure != null) { - this.brokerFastFailure.start(); + if (this.brokerConfig.isEnableControllerMode()) { + scheduleSendHeartbeat(); } + if (brokerConfig.isSkipPreOnline()) { + startServiceWithoutCondition(); + } + } + + protected void scheduleSendHeartbeat() { + scheduledFutures.add(this.brokerHeartbeatExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) { + @Override + public void run0() { + if (isIsolated) { + return; + } + try { + BrokerController.this.sendHeartbeat(); + } catch (Exception e) { + BrokerController.LOG.error("sendHeartbeat Exception", e); + } + } + }, 1000, brokerConfig.getBrokerHeartbeatInterval(), TimeUnit.MILLISECONDS)); } public synchronized void registerIncrementBrokerData(TopicConfig topicConfig, DataVersion dataVersion) { - TopicConfig registerTopicConfig = topicConfig; - if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission()) - || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) { - registerTopicConfig = - new TopicConfig(topicConfig.getTopicName(), topicConfig.getReadQueueNums(), topicConfig.getWriteQueueNums(), - this.brokerConfig.getBrokerPermission()); + this.registerIncrementBrokerData(Collections.singletonList(topicConfig), dataVersion); + } + + public synchronized void registerIncrementBrokerData(List topicConfigList, DataVersion dataVersion) { + if (topicConfigList == null || topicConfigList.isEmpty()) { + return; } - ConcurrentMap topicConfigTable = new ConcurrentHashMap(); - topicConfigTable.put(topicConfig.getTopicName(), registerTopicConfig); - TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); + 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(), + 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) { - TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper(); + + TopicConfigAndMappingSerializeWrapper topicConfigWrapper = new TopicConfigAndMappingSerializeWrapper(); + + topicConfigWrapper.setDataVersion(this.getTopicConfigManager().getDataVersion()); + topicConfigWrapper.setTopicConfigTable(this.getTopicConfigManager().getTopicConfigTable()); + + topicConfigWrapper.setTopicQueueMappingInfoMap(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))); if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission()) || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) { - ConcurrentHashMap topicConfigTable = new ConcurrentHashMap(); + ConcurrentHashMap topicConfigTable = new ConcurrentHashMap<>(); for (TopicConfig topicConfig : topicConfigWrapper.getTopicConfigTable().values()) { TopicConfig tmp = new TopicConfig(topicConfig.getTopicName(), topicConfig.getReadQueueNums(), topicConfig.getWriteQueueNums(), - this.brokerConfig.getBrokerPermission()); + topicConfig.getPerm() & this.brokerConfig.getBrokerPermission(), topicConfig.getTopicSysFlag()); topicConfigTable.put(topicConfig.getTopicName(), tmp); } topicConfigWrapper.setTopicConfigTable(topicConfigTable); @@ -968,13 +1749,19 @@ public synchronized void registerBrokerAll(final boolean checkOrderConfig, boole this.getBrokerAddr(), this.brokerConfig.getBrokerName(), this.brokerConfig.getBrokerId(), - this.brokerConfig.getRegisterBrokerTimeoutMills())) { + this.brokerConfig.getRegisterBrokerTimeoutMills(), + this.brokerConfig.isInBrokerContainer())) { doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper); } } - private void doRegisterBrokerAll(boolean checkOrderConfig, boolean oneway, + protected void doRegisterBrokerAll(boolean checkOrderConfig, boolean oneway, TopicConfigSerializeWrapper topicConfigWrapper) { + + if (shutdown) { + BrokerController.LOG.info("BrokerController#doResterBrokerAll: broker has shutdown, no need to register any more."); + return; + } List registerBrokerResultList = this.brokerOuterAPI.registerBrokerAll( this.brokerConfig.getBrokerClusterName(), this.getBrokerAddr(), @@ -982,23 +1769,86 @@ private void doRegisterBrokerAll(boolean checkOrderConfig, boolean oneway, this.brokerConfig.getBrokerId(), this.getHAServerAddr(), topicConfigWrapper, - this.filterServerManager.buildNewFilterServerList(), + Lists.newArrayList(), oneway, this.brokerConfig.getRegisterBrokerTimeoutMills(), - this.brokerConfig.isCompressedRegister()); + 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)); + } + } - if (registerBrokerResultList.size() > 0) { - RegisterBrokerResult registerBrokerResult = registerBrokerResultList.get(0); + 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; } } } @@ -1007,10 +1857,11 @@ private boolean needRegister(final String clusterName, final String brokerAddr, final String brokerName, final long brokerId, - final int timeoutMills) { + final int timeoutMills, + final boolean isInBrokerContainer) { TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper(); - List changeList = brokerOuterAPI.needRegister(clusterName, brokerAddr, brokerName, brokerId, topicConfigWrapper, timeoutMills); + List changeList = brokerOuterAPI.needRegister(clusterName, brokerAddr, brokerName, brokerId, topicConfigWrapper, timeoutMills, isInBrokerContainer); boolean needRegister = false; for (Boolean changed : changeList) { if (changed) { @@ -1021,6 +1872,212 @@ private boolean needRegister(final String clusterName, 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.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; } @@ -1029,6 +2086,10 @@ public void setTopicConfigManager(TopicConfigManager topicConfigManager) { this.topicConfigManager = topicConfigManager; } + public TopicQueueMappingManager getTopicQueueMappingManager() { + return topicQueueMappingManager; + } + public String getHAServerAddr() { return this.brokerConfig.getBrokerIP2() + ":" + this.messageStoreConfig.getHaListenPort(); } @@ -1041,10 +2102,18 @@ 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; } @@ -1053,8 +2122,8 @@ public BlockingQueue getSendThreadPoolQueue() { return sendThreadPoolQueue; } - public FilterServerManager getFilterServerManager() { - return filterServerManager; + public BlockingQueue getAckThreadPoolQueue() { + return ackThreadPoolQueue; } public BrokerStatsManager getBrokerStatsManager() { @@ -1067,7 +2136,7 @@ public List getSendMessageHookList() { public void registerSendMessageHook(final SendMessageHook hook) { this.sendMessageHookList.add(hook); - log.info("register SendMessageHook Hook, {}", hook.hookName()); + LOG.info("register SendMessageHook Hook, {}", hook.hookName()); } public List getConsumeMessageHookList() { @@ -1076,7 +2145,7 @@ public List getConsumeMessageHookList() { public void registerConsumeMessageHook(final ConsumeMessageHook hook) { this.consumeMessageHookList.add(hook); - log.info("register ConsumeMessageHook Hook, {}", hook.hookName()); + LOG.info("register ConsumeMessageHook Hook, {}", hook.hookName()); } public void registerServerRPCHook(RPCHook rpcHook) { @@ -1092,6 +2161,14 @@ public void setRemotingServer(RemotingServer remotingServer) { this.remotingServer = remotingServer; } + public CountDownLatch getRemotingServerStartLatch() { + return remotingServerStartLatch; + } + + public void setRemotingServerStartLatch(CountDownLatch remotingServerStartLatch) { + this.remotingServerStartLatch = remotingServerStartLatch; + } + public void registerClientRPCHook(RPCHook rpcHook) { this.getBrokerOuterAPI().registerRPCHook(rpcHook); } @@ -1142,7 +2219,6 @@ public void setTransactionalMessageCheckListener( this.transactionalMessageCheckListener = transactionalMessageCheckListener; } - public BlockingQueue getEndTransactionThreadPoolQueue() { return endTransactionThreadPoolQueue; @@ -1152,116 +2228,140 @@ public Map getAccessValidatorMap() { return accessValidatorMap; } - private void handleSlaveSynchronize(BrokerRole role) { - if (role == BrokerRole.SLAVE) { - if (null != slaveSyncFuture) { - slaveSyncFuture.cancel(false); - } - this.slaveSynchronize.setMasterAddr(null); - slaveSyncFuture = this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - BrokerController.this.slaveSynchronize.syncAll(); - } - catch (Throwable e) { - log.error("ScheduledTask SlaveSynchronize syncAll error.", e); - } - } - }, 1000 * 3, 1000 * 10, TimeUnit.MILLISECONDS); - } else { - //handle the slave synchronise - if (null != slaveSyncFuture) { - slaveSyncFuture.cancel(false); - } - this.slaveSynchronize.setMasterAddr(null); - } + public ExecutorService getSendMessageExecutor() { + return sendMessageExecutor; } - public void changeToSlave(int brokerId) { - log.info("Begin to change to slave brokerName={} brokerId={}", brokerConfig.getBrokerName(), brokerId); + public SendMessageProcessor getSendMessageProcessor() { + return sendMessageProcessor; + } - //change the role - brokerConfig.setBrokerId(brokerId == 0 ? 1 : brokerId); //TO DO check - messageStoreConfig.setBrokerRole(BrokerRole.SLAVE); + public QueryAssignmentProcessor getQueryAssignmentProcessor() { + return queryAssignmentProcessor; + } - //handle the scheduled service - try { - this.messageStore.handleScheduleMessageService(BrokerRole.SLAVE); - } catch (Throwable t) { - log.error("[MONITOR] handleScheduleMessageService failed when changing to slave", t); - } + public TopicQueueMappingCleanService getTopicQueueMappingCleanService() { + return topicQueueMappingCleanService; + } - //handle the transactional service - try { - this.shutdownProcessorByHa(); - } catch (Throwable t) { - log.error("[MONITOR] shutdownProcessorByHa failed when changing to slave", t); - } + public ExecutorService getAdminBrokerExecutor() { + return adminBrokerExecutor; + } - //handle the slave synchronise - handleSlaveSynchronize(BrokerRole.SLAVE); + public BlockingQueue getLitePullThreadPoolQueue() { + return litePullThreadPoolQueue; + } - try { - this.registerBrokerAll(true, true, true); - } catch (Throwable ignored) { + public ShutdownHook getShutdownHook() { + return shutdownHook; + } - } - log.info("Finish to change to slave brokerName={} brokerId={}", brokerConfig.getBrokerName(), brokerId); + 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 void changeToMaster(BrokerRole role) { - if (role == BrokerRole.SLAVE) { - return; - } - log.info("Begin to change to master brokerName={}", brokerConfig.getBrokerName()); + public BrokerMemberGroup getBrokerMemberGroup() { + return this.brokerMemberGroup; + } - //handle the slave synchronise - handleSlaveSynchronize(role); + public int getListenPort() { + return this.nettyServerConfig.getListenPort(); + } - //handle the scheduled service - try { - this.messageStore.handleScheduleMessageService(role); - } catch (Throwable t) { - log.error("[MONITOR] handleScheduleMessageService failed when changing to master", t); - } + public List getBrokerAttachedPlugins() { + return brokerAttachedPlugins; + } - //handle the transactional service - try { - this.startProcessorByHa(BrokerRole.SYNC_MASTER); - } catch (Throwable t) { - log.error("[MONITOR] startProcessorByHa failed when changing to master", t); - } + public EscapeBridge getEscapeBridge() { + return escapeBridge; + } - //if the operations above are totally successful, we change to master - brokerConfig.setBrokerId(0); //TO DO check - messageStoreConfig.setBrokerRole(role); + public long getShouldStartTime() { + return shouldStartTime; + } - try { - this.registerBrokerAll(true, true, true); - } catch (Throwable ignored) { + public BrokerPreOnlineService getBrokerPreOnlineService() { + return brokerPreOnlineService; + } - } - log.info("Finish to change to master brokerName={}", brokerConfig.getBrokerName()); + public EndTransactionProcessor getEndTransactionProcessor() { + return endTransactionProcessor; } - private void startProcessorByHa(BrokerRole role) { - if (BrokerRole.SLAVE != role) { - if (this.transactionalMessageCheckService != null) { - this.transactionalMessageCheckService.start(); - } - } + public boolean isScheduleServiceStart() { + return isScheduleServiceStart; } - private void shutdownProcessorByHa() { - if (this.transactionalMessageCheckService != null) { - this.transactionalMessageCheckService.shutdown(true); - } + public boolean isTransactionCheckServiceStart() { + return isTransactionCheckServiceStart; } - public ExecutorService getPutMessageFutureExecutor() { - return putMessageFutureExecutor; + 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; } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerPathConfigHelper.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerPathConfigHelper.java index 321c800b7ce..cea321ef784 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerPathConfigHelper.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerPathConfigHelper.java @@ -35,6 +35,10 @@ public static String getTopicConfigPath(final String rootDir) { return rootDir + File.separator + "config" + File.separator + "topics.json"; } + public static String getTopicQueueMappingPath(final String rootDir) { + return rootDir + File.separator + "config" + File.separator + "topicQueueMapping.json"; + } + public static String getConsumerOffsetPath(final String rootDir) { return rootDir + File.separator + "config" + File.separator + "consumerOffset.json"; } @@ -43,11 +47,25 @@ public static String getLmqConsumerOffsetPath(final String rootDir) { return rootDir + File.separator + "config" + File.separator + "lmqConsumerOffset.json"; } + public static String getConsumerOrderInfoPath(final String rootDir) { + return rootDir + File.separator + "config" + File.separator + "consumerOrderInfo.json"; + } + public static String getSubscriptionGroupPath(final String rootDir) { return rootDir + File.separator + "config" + File.separator + "subscriptionGroup.json"; } + public static String getTimerCheckPath(final String rootDir) { + return rootDir + File.separator + "config" + File.separator + "timercheck"; + } + public static String getTimerMetricsPath(final String rootDir) { + return rootDir + File.separator + "config" + File.separator + "timermetrics"; + } public static String getConsumerFilterPath(final String rootDir) { return rootDir + File.separator + "config" + File.separator + "consumerFilter.json"; } + + public static String getMessageRequestModePath(final String rootDir) { + return rootDir + File.separator + "config" + File.separator + "messageRequestMode.json"; + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerPreOnlineService.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerPreOnlineService.java new file mode 100644 index 00000000000..de2ccb29399 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerPreOnlineService.java @@ -0,0 +1,287 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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(); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerStartup.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerStartup.java index 19e618ba2da..3151683161d 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerStartup.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerStartup.java @@ -16,42 +16,35 @@ */ package org.apache.rocketmq.broker; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.joran.JoranConfigurator; +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.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.cli.PosixParser; +import org.apache.commons.lang3.StringUtils; 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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.remoting.common.RemotingUtil; -import org.apache.rocketmq.remoting.common.TlsMode; +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.TlsSystemConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.srvutil.ServerUtil; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; -import org.slf4j.LoggerFactory; - -import java.io.BufferedInputStream; -import java.io.FileInputStream; -import java.io.InputStream; -import java.util.Properties; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_ENABLE; public class BrokerStartup { - public static Properties properties = null; - public static CommandLine commandLine = null; - public static String configFile = null; - public static InternalLogger log; + + public static Logger log; + public static final SystemConfigFileHelper CONFIG_FILE_HELPER = new SystemConfigFileHelper(); public static void main(String[] args) { start(createBrokerController(args)); @@ -59,11 +52,11 @@ public static void main(String[] args) { public static BrokerController start(BrokerController controller) { try { - controller.start(); - String tip = "The broker[" + controller.getBrokerConfig().getBrokerName() + ", " - + controller.getBrokerAddr() + "] boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer(); + 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(); @@ -86,168 +79,175 @@ public static void shutdown(final BrokerController controller) { } } - public static BrokerController createBrokerController(String[] args) { + public static BrokerController buildBrokerController(String[] args) throws Exception { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION)); - try { - //PackageConflictDetect.detectFastjson(); - Options options = ServerUtil.buildCommandlineOptions(new Options()); - commandLine = ServerUtil.parseCmdLine("mqbroker", args, buildCommandlineOptions(options), - new PosixParser()); - if (null == commandLine) { - System.exit(-1); - } - - final BrokerConfig brokerConfig = new BrokerConfig(); - final NettyServerConfig nettyServerConfig = new NettyServerConfig(); - final NettyClientConfig nettyClientConfig = new NettyClientConfig(); - - nettyClientConfig.setUseTLS(Boolean.parseBoolean(System.getProperty(TLS_ENABLE, - String.valueOf(TlsSystemConfig.tlsMode == TlsMode.ENFORCING)))); - nettyServerConfig.setListenPort(10911); - final MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); - - if (BrokerRole.SLAVE == messageStoreConfig.getBrokerRole()) { - int ratio = messageStoreConfig.getAccessMessageInMemoryMaxRatio() - 10; - messageStoreConfig.setAccessMessageInMemoryMaxRatio(ratio); - } + final BrokerConfig brokerConfig = new BrokerConfig(); + final NettyServerConfig nettyServerConfig = new NettyServerConfig(); + final NettyClientConfig nettyClientConfig = new NettyClientConfig(); + final MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); + nettyServerConfig.setListenPort(10911); + messageStoreConfig.setHaListenPort(0); + + Options options = ServerUtil.buildCommandlineOptions(new Options()); + CommandLine commandLine = ServerUtil.parseCmdLine( + "mqbroker", args, buildCommandlineOptions(options), new DefaultParser()); + if (null == commandLine) { + System.exit(-1); + } - if (commandLine.hasOption('c')) { - String file = commandLine.getOptionValue('c'); - if (file != null) { - configFile = file; - InputStream in = new BufferedInputStream(new FileInputStream(file)); - properties = new Properties(); - properties.load(in); - - properties2SystemEnv(properties); - MixAll.properties2Object(properties, brokerConfig); - MixAll.properties2Object(properties, nettyServerConfig); - MixAll.properties2Object(properties, nettyClientConfig); - MixAll.properties2Object(properties, messageStoreConfig); - - BrokerPathConfigHelper.setBrokerConfigPath(file); - in.close(); - } + Properties properties = null; + if (commandLine.hasOption('c')) { + String file = commandLine.getOptionValue('c'); + if (file != null) { + CONFIG_FILE_HELPER.setFile(file); + BrokerPathConfigHelper.setBrokerConfigPath(file); + properties = CONFIG_FILE_HELPER.loadConfig(); } + } - MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), brokerConfig); + if (properties != null) { + properties2SystemEnv(properties); + MixAll.properties2Object(properties, brokerConfig); + MixAll.properties2Object(properties, nettyServerConfig); + MixAll.properties2Object(properties, nettyClientConfig); + MixAll.properties2Object(properties, messageStoreConfig); + } - 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); - } + MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), brokerConfig); + 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); + } - String namesrvAddr = brokerConfig.getNamesrvAddr(); - if (null != namesrvAddr) { - try { - String[] addrArray = namesrvAddr.split(";"); - for (String addr : addrArray) { - RemotingUtil.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); + // 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); } + } + if (BrokerRole.SLAVE == messageStoreConfig.getBrokerRole()) { + int ratio = messageStoreConfig.getAccessMessageInMemoryMaxRatio() - 10; + messageStoreConfig.setAccessMessageInMemoryMaxRatio(ratio); + } + + // 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() <= 0) { - System.out.printf("Slave's brokerId must be > 0"); + 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 (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); - LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); - JoranConfigurator configurator = new JoranConfigurator(); - configurator.setContext(lc); - lc.reset(); - 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()); - } - configurator.doConfigure(brokerConfig.getRocketmqHome() + "/conf/logback_broker.xml"); - - if (commandLine.hasOption('p')) { - InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.BROKER_CONSOLE_NAME); - MixAll.printObjectProperties(console, brokerConfig); - MixAll.printObjectProperties(console, nettyServerConfig); - MixAll.printObjectProperties(console, nettyClientConfig); - MixAll.printObjectProperties(console, messageStoreConfig); - System.exit(0); - } else if (commandLine.hasOption('m')) { - InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.BROKER_CONSOLE_NAME); - MixAll.printObjectProperties(console, brokerConfig, true); - MixAll.printObjectProperties(console, nettyServerConfig, true); - MixAll.printObjectProperties(console, nettyClientConfig, true); - MixAll.printObjectProperties(console, messageStoreConfig, true); - System.exit(0); - } + } + + 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()); + } + + if (commandLine.hasOption('p')) { + Logger console = LoggerFactory.getLogger(LoggerName.BROKER_CONSOLE_NAME); + MixAll.printObjectProperties(console, brokerConfig); + MixAll.printObjectProperties(console, nettyServerConfig); + MixAll.printObjectProperties(console, nettyClientConfig); + MixAll.printObjectProperties(console, messageStoreConfig); + System.exit(0); + } else if (commandLine.hasOption('m')) { + Logger console = LoggerFactory.getLogger(LoggerName.BROKER_CONSOLE_NAME); + MixAll.printObjectProperties(console, brokerConfig, true); + MixAll.printObjectProperties(console, nettyServerConfig, true); + MixAll.printObjectProperties(console, nettyClientConfig, true); + MixAll.printObjectProperties(console, messageStoreConfig, true); + System.exit(0); + } + + log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + MixAll.printObjectProperties(log, brokerConfig); + MixAll.printObjectProperties(log, nettyServerConfig); + MixAll.printObjectProperties(log, nettyClientConfig); + MixAll.printObjectProperties(log, messageStoreConfig); - log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); - MixAll.printObjectProperties(log, brokerConfig); - MixAll.printObjectProperties(log, nettyServerConfig); - MixAll.printObjectProperties(log, nettyClientConfig); - MixAll.printObjectProperties(log, messageStoreConfig); + final BrokerController controller = new BrokerController( + brokerConfig, nettyServerConfig, nettyClientConfig, messageStoreConfig); - final BrokerController controller = new BrokerController( - brokerConfig, - nettyServerConfig, - nettyClientConfig, - messageStoreConfig); - // remember all configs to prevent discard - controller.getConfiguration().registerConfig(properties); + // Remember all configs to prevent discard + controller.getConfiguration().registerConfig(properties); + 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 { + BrokerController controller = buildBrokerController(args); boolean initResult = controller.initialize(); if (!initResult) { controller.shutdown(); System.exit(-3); } - - 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(); - controller.shutdown(); - long consumingTimeTotal = System.currentTimeMillis() - beginTime; - log.info("Shutdown hook over, consuming total time(ms): {}", consumingTimeTotal); - } - } - } - }, "ShutdownHook")); - + Runtime.getRuntime().addShutdownHook(new Thread(buildShutdownHook(controller))); return controller; } catch (Throwable e) { e.printStackTrace(); System.exit(-1); } - return null; } @@ -276,4 +276,33 @@ private static Options buildCommandlineOptions(final Options options) { 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 { + InputStream in = new BufferedInputStream(Files.newInputStream(Paths.get(file))); + Properties properties = new Properties(); + properties.load(in); + in.close(); + 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; + } + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/ShutdownHook.java b/broker/src/main/java/org/apache/rocketmq/broker/ShutdownHook.java new file mode 100644 index 00000000000..63567f8c3d9 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/ShutdownHook.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ClientHousekeepingService.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ClientHousekeepingService.java index d536db5055e..98e5f450f3f 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/client/ClientHousekeepingService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ClientHousekeepingService.java @@ -17,25 +17,26 @@ package org.apache.rocketmq.broker.client; import io.netty.channel.Channel; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; 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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; - private ScheduledExecutorService scheduledExecutorService = Executors - .newSingleThreadScheduledExecutor(new ThreadFactoryImpl("ClientHousekeepingScheduledThread")); + private ScheduledExecutorService scheduledExecutorService; public ClientHousekeepingService(final BrokerController brokerController) { this.brokerController = brokerController; + scheduledExecutorService = new ScheduledThreadPoolExecutor(1, + new ThreadFactoryImpl("ClientHousekeepingScheduledThread", brokerController.getBrokerIdentity())); } public void start() { @@ -55,7 +56,6 @@ public void run() { private void scanExceptionChannel() { this.brokerController.getProducerManager().scanNotActiveChannel(); this.brokerController.getConsumerManager().scanNotActiveChannel(); - this.brokerController.getFilterServerManager().scanNotActiveChannel(); } public void shutdown() { @@ -64,27 +64,27 @@ public void 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.getFilterServerManager().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.getFilterServerManager().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.getFilterServerManager().doChannelCloseEvent(remoteAddr, channel); + this.brokerController.getBrokerStatsManager().incChannelIdleNum(); } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupEvent.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupEvent.java index 717fb7085e5..6c0a58db061 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupEvent.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupEvent.java @@ -29,5 +29,13 @@ public enum ConsumerGroupEvent { /** * The group of consumer is registered. */ - REGISTER + REGISTER, + /** + * The client of this consumer is new registered. + */ + CLIENT_REGISTER, + /** + * The client of this consumer is unregistered. + */ + CLIENT_UNREGISTER } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupInfo.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupInfo.java index c90d4942ac2..867b9c72027 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupInfo.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupInfo.java @@ -26,19 +26,19 @@ import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final String groupName; private final ConcurrentMap subscriptionTable = - new ConcurrentHashMap(); + new ConcurrentHashMap<>(); private final ConcurrentMap channelInfoTable = - new ConcurrentHashMap(16); + new ConcurrentHashMap<>(16); private volatile ConsumeType consumeType; private volatile MessageModel messageModel; private volatile ConsumeFromWhere consumeFromWhere; @@ -52,6 +52,10 @@ public ConsumerGroupInfo(String groupName, ConsumeType consumeType, 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()) { @@ -68,6 +72,10 @@ public ConcurrentMap getSubscriptionTable() { return subscriptionTable; } + public ClientChannelInfo findChannel(final Channel channel) { + return this.channelInfoTable.get(channel); + } + public ConcurrentMap getChannelInfoTable() { return channelInfoTable; } @@ -94,25 +102,35 @@ public List getAllClientId() { return result; } - public void unregisterChannel(final ClientChannelInfo clientChannelInfo) { + 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 boolean doChannelCloseEvent(final String remoteAddr, final Channel channel) { + 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 true; } - return false; + 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; @@ -132,9 +150,9 @@ public boolean updateChannel(final ClientChannelInfo infoNew, ConsumeType consum infoOld = infoNew; } else { if (!infoOld.getClientId().equals(infoNew.getClientId())) { - log.error("[BUG] consumer channel exist in broker, but clientId not equal. GROUP: {} OLD: {} NEW: {} ", - this.groupName, - infoOld.toString(), + 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); } @@ -146,6 +164,12 @@ public boolean updateChannel(final ClientChannelInfo infoNew, ConsumeType consum 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; diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerIdsChangeListener.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerIdsChangeListener.java index 831e2932733..144092ca680 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerIdsChangeListener.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerIdsChangeListener.java @@ -19,4 +19,6 @@ public interface ConsumerIdsChangeListener { void handle(ConsumerGroupEvent event, String group, Object... args); + + void shutdown(); } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerManager.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerManager.java index cb6065540af..42e71e7e997 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerManager.java @@ -17,31 +17,51 @@ 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 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.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; +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.common.RemotingUtil; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); - private static final long CHANNEL_EXPIRED_TIMEOUT = 1000 * 120; - private final ConcurrentMap consumerTable = - new ConcurrentHashMap(1024); - private final ConsumerIdsChangeListener consumerIdsChangeListener; + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + private final ConcurrentMap consumerTable = + 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; - public ConsumerManager(final ConsumerIdsChangeListener consumerIdsChangeListener) { - this.consumerIdsChangeListener = consumerIdsChangeListener; + public ConsumerManager(final ConsumerIdsChangeListener consumerIdsChangeListener, long expiredTimeout) { + this.consumerIdsChangeListenerList.add(consumerIdsChangeListener); + this.brokerStatsManager = null; + this.channelExpiredTimeout = expiredTimeout; + this.subscriptionExpiredTimeout = expiredTimeout; + } + + 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(); } public ClientChannelInfo findChannel(final String group, final String clientId) { @@ -52,17 +72,51 @@ public ClientChannelInfo findChannel(final String group, final String 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) { - ConsumerGroupInfo consumerGroupInfo = this.getConsumerGroupInfo(group); + return findSubscriptionData(group, topic, true); + } + + public SubscriptionData findSubscriptionData(final String group, final String topic, + boolean fromCompensationTable) { + ConsumerGroupInfo consumerGroupInfo = getConsumerGroupInfo(group, false); if (consumerGroupInfo != null) { - return consumerGroupInfo.findSubscriptionData(topic); + 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 this.consumerTable.get(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) { @@ -74,33 +128,58 @@ public int findSubscriptionDataCount(final String group) { return 0; } - public void doChannelCloseEvent(final String remoteAddr, final Channel channel) { + public boolean doChannelCloseEvent(final String remoteAddr, final Channel channel) { + boolean removed = false; Iterator> it = this.consumerTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); ConsumerGroupInfo info = next.getValue(); - boolean removed = info.doChannelCloseEvent(remoteAddr, channel); - if (removed) { + ClientChannelInfo clientChannelInfo = info.doChannelCloseEvent(remoteAddr, channel); + if (clientChannelInfo != null) { + callConsumerIdsChangeListener(ConsumerGroupEvent.CLIENT_UNREGISTER, next.getKey(), clientChannelInfo, info.getSubscribeTopics()); if (info.getChannelInfoTable().isEmpty()) { ConsumerGroupInfo remove = this.consumerTable.remove(next.getKey()); if (remove != null) { - log.info("unregister consumer ok, no any connection, and remove consumer group, {}", + LOGGER.info("unregister consumer ok, no any connection, and remove consumer group, {}", next.getKey()); - this.consumerIdsChangeListener.handle(ConsumerGroupEvent.UNREGISTER, next.getKey()); + callConsumerIdsChangeListener(ConsumerGroupEvent.UNREGISTER, next.getKey()); } } - this.consumerIdsChangeListener.handle(ConsumerGroupEvent.CHANGE, next.getKey(), info.getAllChannel()); + callConsumerIdsChangeListener(ConsumerGroupEvent.CHANGE, next.getKey(), info.getAllChannel()); } } + return removed; + } + + // 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) { + callConsumerIdsChangeListener(ConsumerGroupEvent.CLIENT_REGISTER, group, clientChannelInfo, + subList.stream().map(SubscriptionData::getTopic).collect(Collectors.toSet())); ConsumerGroupInfo tmp = new ConsumerGroupInfo(group, consumeType, messageModel, consumeFromWhere); ConsumerGroupInfo prev = this.consumerTable.putIfAbsent(group, tmp); consumerGroupInfo = prev != null ? prev : tmp; @@ -109,35 +188,86 @@ public boolean registerConsumer(final String group, final ClientChannelInfo clie boolean r1 = consumerGroupInfo.updateChannel(clientChannelInfo, consumeType, messageModel, consumeFromWhere); - boolean r2 = consumerGroupInfo.updateSubscription(subList); + boolean r2 = false; + if (updateSubscription) { + r2 = consumerGroupInfo.updateSubscription(subList); + } if (r1 || r2) { if (isNotifyConsumerIdsChangedEnable) { - this.consumerIdsChangeListener.handle(ConsumerGroupEvent.CHANGE, group, consumerGroupInfo.getAllChannel()); + callConsumerIdsChangeListener(ConsumerGroupEvent.CHANGE, group, consumerGroupInfo.getAllChannel()); } } + if (null != this.brokerStatsManager) { + this.brokerStatsManager.incConsumerRegisterTime((int) (System.currentTimeMillis() - start)); + } - this.consumerIdsChangeListener.handle(ConsumerGroupEvent.REGISTER, group, subList); + 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; + } + boolean updateChannelRst = consumerGroupInfo.updateChannel(clientChannelInfo, consumeType, messageModel, consumeFromWhere); + if (updateChannelRst && isNotifyConsumerIdsChangedEnable) { + 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) { - consumerGroupInfo.unregisterChannel(clientChannelInfo); + 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) { - log.info("unregister consumer ok, no any connection, and remove consumer group, {}", group); + LOGGER.info("unregister consumer ok, no any connection, and remove consumer group, {}", group); - this.consumerIdsChangeListener.handle(ConsumerGroupEvent.UNREGISTER, group); + callConsumerIdsChangeListener(ConsumerGroupEvent.UNREGISTER, group); } } if (isNotifyConsumerIdsChangedEnable) { - this.consumerIdsChangeListener.handle(ConsumerGroupEvent.CHANGE, group, consumerGroupInfo.getAllChannel()); + 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); } } @@ -155,22 +285,24 @@ public void scanNotActiveChannel() { Entry nextChannel = itChannel.next(); ClientChannelInfo clientChannelInfo = nextChannel.getValue(); long diff = System.currentTimeMillis() - clientChannelInfo.getLastUpdateTimestamp(); - if (diff > CHANNEL_EXPIRED_TIMEOUT) { - log.warn( + if (diff > channelExpiredTimeout) { + LOGGER.warn( "SCAN: remove expired channel from ConsumerManager consumerTable. channel={}, consumerGroup={}", RemotingHelper.parseChannelRemoteAddr(clientChannelInfo.getChannel()), group); - RemotingUtil.closeChannel(clientChannelInfo.getChannel()); + callConsumerIdsChangeListener(ConsumerGroupEvent.CLIENT_UNREGISTER, group, clientChannelInfo, consumerGroupInfo.getSubscribeTopics()); + RemotingHelper.closeChannel(clientChannelInfo.getChannel()); itChannel.remove(); } } if (channelInfoTable.isEmpty()) { - log.warn( + LOGGER.warn( "SCAN: remove expired channel from ConsumerManager consumerTable, all clear, consumerGroup={}", group); it.remove(); } } + removeExpireConsumerGroupInfo(); } public HashSet queryTopicConsumeByWho(final String topic) { @@ -186,4 +318,18 @@ public HashSet queryTopicConsumeByWho(final String topic) { } return groups; } + + 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); + } + } + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/DefaultConsumerIdsChangeListener.java b/broker/src/main/java/org/apache/rocketmq/broker/client/DefaultConsumerIdsChangeListener.java index d716a339fa0..2ce036a0ffc 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/client/DefaultConsumerIdsChangeListener.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/client/DefaultConsumerIdsChangeListener.java @@ -17,18 +17,45 @@ 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.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; +import org.apache.rocketmq.common.AbstractBrokerRunnable; +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 = new ScheduledThreadPoolExecutor(1, + ThreadUtils.newGenericThreadFactory("DefaultConsumerIdsChangeListener", true)); + + private ConcurrentHashMap> consumerChannelMap = new ConcurrentHashMap<>(cacheSize); public DefaultConsumerIdsChangeListener(BrokerController brokerController) { this.brokerController = brokerController; + + scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(brokerController.getBrokerConfig()) { + @Override + public void run0() { + try { + notifyConsumerChange(); + } catch (Exception e) { + log.error( + "DefaultConsumerIdsChangeListen#notifyConsumerChange: unexpected error occurs", e); + } + } + }, 30, 15, TimeUnit.SECONDS); } @Override @@ -43,8 +70,12 @@ public void handle(ConsumerGroupEvent event, String group, Object... args) { } List channels = (List) args[0]; if (channels != null && brokerController.getBrokerConfig().isNotifyConsumerIdsChangedEnable()) { - for (Channel chl : channels) { - this.brokerController.getBroker2Client().notifyConsumerIdsChanged(chl, group); + if (this.brokerController.getBrokerConfig().isRealTimeNotifyConsumerChange()) { + for (Channel chl : channels) { + this.brokerController.getBroker2Client().notifyConsumerIdsChanged(chl, group); + } + } else { + consumerChannelMap.put(group, channels); } } break; @@ -58,8 +89,41 @@ public void handle(ConsumerGroupEvent event, String group, Object... args) { 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(); + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerChangeListener.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerChangeListener.java new file mode 100644 index 00000000000..f8183d33fa0 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerChangeListener.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerGroupEvent.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerGroupEvent.java new file mode 100644 index 00000000000..cbf27ce61e6 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerGroupEvent.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerManager.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerManager.java index 4bd00eff181..52d67bf2821 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerManager.java @@ -18,37 +18,88 @@ 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.CopyOnWriteArrayList; import org.apache.rocketmq.broker.util.PositiveAtomicCounter; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.common.RemotingUtil; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + 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 ConcurrentHashMap> groupChannelTable = new ConcurrentHashMap<>(); private final ConcurrentHashMap clientChannelTable = new ConcurrentHashMap<>(); + protected final BrokerStatsManager brokerStatsManager; private PositiveAtomicCounter positiveAtomicCounter = new PositiveAtomicCounter(); + private final List producerChangeListenerList = new CopyOnWriteArrayList<>(); public ProducerManager() { + this.brokerStatsManager = null; + } + + public ProducerManager(final BrokerStatsManager brokerStatsManager) { + this.brokerStatsManager = brokerStatsManager; + } + + public int groupSize() { + return this.groupChannelTable.size(); + } + + public boolean groupOnline(String group) { + Map channels = this.groupChannelTable.get(group); + return channels != null && !channels.isEmpty(); } public ConcurrentHashMap> 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() { - for (final Map.Entry> entry : this.groupChannelTable - .entrySet()) { + Iterator>> iterator = this.groupChannelTable.entrySet().iterator(); + + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + final String group = entry.getKey(); final ConcurrentHashMap chlMap = entry.getValue(); @@ -63,15 +114,23 @@ public void scanNotActiveChannel() { it.remove(); clientChannelTable.remove(info.getClientId()); log.warn( - "SCAN: remove expired channel[{}] from ProducerManager groupChannelTable, producer group name: {}", + "ProducerManager#scanNotActiveChannel: remove expired channel[{}] from ProducerManager groupChannelTable, producer group name: {}", RemotingHelper.parseChannelRemoteAddr(info.getChannel()), group); - RemotingUtil.closeChannel(info.getChannel()); + 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 synchronized void doChannelCloseEvent(final String remoteAddr, final Channel channel) { + public synchronized boolean doChannelCloseEvent(final String remoteAddr, final Channel channel) { + boolean removed = false; if (channel != null) { for (final Map.Entry> entry : this.groupChannelTable .entrySet()) { @@ -82,13 +141,23 @@ public synchronized void doChannelCloseEvent(final String remoteAddr, final Chan 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()) { + ConcurrentHashMap 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 synchronized void registerProducer(final String group, final ClientChannelInfo clientChannelInfo) { @@ -122,10 +191,12 @@ public synchronized void unregisterProducer(final String group, final ClientChan 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); } } @@ -174,4 +245,19 @@ public Channel getAvailableChannel(String groupId) { 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); + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/net/Broker2Client.java b/broker/src/main/java/org/apache/rocketmq/broker/client/net/Broker2Client.java index 4d28cd819f2..8d95d843dba 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/client/net/Broker2Client.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/client/net/Broker2Client.java @@ -17,6 +17,12 @@ 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; @@ -28,31 +34,24 @@ 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.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.body.GetConsumerStatusBody; -import org.apache.rocketmq.common.protocol.body.ResetOffsetBody; -import org.apache.rocketmq.common.protocol.body.ResetOffsetBodyForC; -import org.apache.rocketmq.common.protocol.header.CheckTransactionStateRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumerStatusRequestHeader; -import org.apache.rocketmq.common.protocol.header.NotifyConsumerIdsChangedRequestHeader; -import org.apache.rocketmq.common.protocol.header.ResetOffsetRequestHeader; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.protocol.RemotingCommand; - -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.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.ResetOffsetRequestHeader; public class Broker2Client { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; public Broker2Client(BrokerController brokerController) { @@ -76,7 +75,7 @@ public void checkProducerTransactionState( } public RemotingCommand callClient(final Channel channel, - final RemotingCommand request + final RemotingCommand request ) throws RemotingSendRequestException, RemotingTimeoutException, InterruptedException { return this.brokerController.getRemotingServer().invokeSync(channel, request, 10000); } @@ -101,12 +100,13 @@ public void notifyConsumerIdsChanged( } } + public RemotingCommand resetOffset(String topic, String group, long timeStamp, boolean isForce) { return resetOffset(topic, group, timeStamp, isForce, false); } public RemotingCommand resetOffset(String topic, String group, long timeStamp, boolean isForce, - boolean isC) { + boolean isC) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); @@ -117,7 +117,7 @@ public RemotingCommand resetOffset(String topic, String group, long timeStamp, b return response; } - Map offsetTable = new HashMap(); + Map offsetTable = new HashMap<>(); for (int i = 0; i < topicConfig.getWriteQueueNums(); i++) { MessageQueue mq = new MessageQueue(); @@ -237,8 +237,7 @@ public RemotingCommand getConsumeStatus(String topic, String group, String origi RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_STATUS_FROM_CLIENT, requestHeader); - Map> consumerStatusTable = - new HashMap>(); + Map> consumerStatusTable = new HashMap<>(); ConcurrentMap channelInfoTable = this.brokerController.getConsumerManager().getConsumerGroupInfo(group).getChannelInfoTable(); if (null == channelInfoTable || channelInfoTable.isEmpty()) { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/rebalance/RebalanceLockManager.java b/broker/src/main/java/org/apache/rocketmq/broker/client/rebalance/RebalanceLockManager.java index 678b1f54a15..bf7d0964bef 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/client/rebalance/RebalanceLockManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/client/rebalance/RebalanceLockManager.java @@ -16,24 +16,38 @@ */ 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; -import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.message.MessageQueue; public class RebalanceLockManager { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.REBALANCE_LOCK_LOGGER_NAME); + 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); + 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) { @@ -52,10 +66,9 @@ public boolean tryLock(final String group, final MessageQueue mq, final String c lockEntry = new LockEntry(); lockEntry.setClientId(clientId); groupValue.put(mq, lockEntry); - log.info("tryLock, message queue not locked, I got it. Group: {} NewClientId: {} {}", - group, - clientId, - mq); + log.info( + "RebalanceLockManager#tryLock: lock a message queue which has not been locked yet, " + + "group={}, clientId={}, mq={}", group, clientId, mq); } if (lockEntry.isLocked(clientId)) { @@ -69,26 +82,21 @@ public boolean tryLock(final String group, final MessageQueue mq, final String c lockEntry.setClientId(clientId); lockEntry.setLastUpdateTimestamp(System.currentTimeMillis()); log.warn( - "tryLock, message queue lock expired, I got it. Group: {} OldClientId: {} NewClientId: {} {}", - group, - oldClientId, - clientId, - mq); + "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( - "tryLock, message queue locked by other client. Group: {} OtherClientId: {} NewClientId: {} {}", - group, - oldClientId, - clientId, - mq); + "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("putMessage exception", e); + log.error("RebalanceLockManager#tryLock: unexpected error, group={}, mq={}, clientId={}", group, mq, + clientId, e); } } else { @@ -116,8 +124,8 @@ private boolean isLocked(final String group, final MessageQueue mq, final String public Set tryLockBatch(final String group, final Set mqs, final String clientId) { - Set lockedMqs = new HashSet(mqs.size()); - Set notLockedMqs = new HashSet(mqs.size()); + Set lockedMqs = new HashSet<>(mqs.size()); + Set notLockedMqs = new HashSet<>(mqs.size()); for (MessageQueue mq : mqs) { if (this.isLocked(group, mq, clientId)) { @@ -144,10 +152,8 @@ public Set tryLockBatch(final String group, final Set tryLockBatch(final String group, final Set mqs, final S if (null != lockEntry) { if (lockEntry.getClientId().equals(clientId)) { groupValue.remove(mq); - log.info("unlockBatch, Group: {} {} {}", - group, - mq, - clientId); + log.info("RebalanceLockManager#unlockBatch: unlock mq, group={}, clientId={}, mqs={}", + group, clientId, mq); } else { - log.warn("unlockBatch, but mq locked by other client: {}, Group: {} {} {}", - lockEntry.getClientId(), - group, - mq, - clientId); + log.warn( + "RebalanceLockManager#unlockBatch: mq locked by other client, group={}, locked " + + "clientId={}, current clientId={}, mqs={}", group, lockEntry.getClientId(), + clientId, mq); } } else { - log.warn("unlockBatch, but mq not locked, Group: {} {} {}", - group, - mq, - clientId); + log.warn("RebalanceLockManager#unlockBatch: mq not locked, group={}, clientId={}, mq={}", + group, clientId, mq); } } } else { - log.warn("unlockBatch, group not exist, Group: {} {}", - group, - clientId); + log.warn("RebalanceLockManager#unlockBatch: group not exist, group={}, clientId={}, mqs={}", group, + clientId, mqs); } } finally { this.lock.unlock(); } } catch (InterruptedException e) { - log.error("putMessage exception", e); + log.error("RebalanceLockManager#unlockBatch: unexpected error, group={}, mqs={}, clientId={}", group, mqs, + clientId); } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/coldctr/ColdCtrStrategy.java b/broker/src/main/java/org/apache/rocketmq/broker/coldctr/ColdCtrStrategy.java new file mode 100644 index 00000000000..11fa0e707f4 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/coldctr/ColdCtrStrategy.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/coldctr/ColdDataCgCtrService.java b/broker/src/main/java/org/apache/rocketmq/broker/coldctr/ColdDataCgCtrService.java new file mode 100644 index 00000000000..dd9278fb755 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/coldctr/ColdDataCgCtrService.java @@ -0,0 +1,250 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.fastjson.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.isSysConsumerGroupForNoColdReadLimit(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)); + } + +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/coldctr/ColdDataPullRequestHoldService.java b/broker/src/main/java/org/apache/rocketmq/broker/coldctr/ColdDataPullRequestHoldService.java new file mode 100644 index 00000000000..c38d886fd33 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/coldctr/ColdDataPullRequestHoldService.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); + } + +} \ No newline at end of file diff --git a/broker/src/main/java/org/apache/rocketmq/broker/coldctr/PIDAdaptiveColdCtrStrategy.java b/broker/src/main/java/org/apache/rocketmq/broker/coldctr/PIDAdaptiveColdCtrStrategy.java new file mode 100644 index 00000000000..87d9789f71f --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/coldctr/PIDAdaptiveColdCtrStrategy.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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(); + } + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/coldctr/SimpleColdCtrStrategy.java b/broker/src/main/java/org/apache/rocketmq/broker/coldctr/SimpleColdCtrStrategy.java new file mode 100644 index 00000000000..f26a242f9aa --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/coldctr/SimpleColdCtrStrategy.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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) { + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java new file mode 100644 index 00000000000..abae7cdb01a --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java @@ -0,0 +1,877 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +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.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 = Executors.newScheduledThreadPool(3, new ThreadFactoryImpl("ReplicasManager_ScheduledService_", brokerController.getBrokerIdentity())); + this.executorService = Executors.newFixedThreadPool(3, new ThreadFactoryImpl("ReplicasManager_ExecutorService_", brokerController.getBrokerIdentity())); + this.scanExecutor = new ThreadPoolExecutor(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) { + 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) { + 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: {}", e, tempBrokerMetadata.getBrokerId()); + 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.setBrokerControllerId(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.setBrokerControllerId(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 medata. + */ + 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(() -> { + checkSyncStateSetAndDoReport(); + }, 3 * 1000, this.brokerConfig.getCheckSyncStateSetPeriod(), TimeUnit.MILLISECONDS); + } + + private void checkSyncStateSetAndDoReport() { + 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); + } + + 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() { + 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); + } + } + }); + } + } + + private void updateControllerAddr() { + if (brokerConfig.isFetchControllerAddrByDnsLookup()) { + this.controllerAddresses = brokerOuterAPI.dnsLookupAddressByDomain(this.brokerConfig.getControllerAddr()); + } 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); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/dledger/DLedgerRoleChangeHandler.java b/broker/src/main/java/org/apache/rocketmq/broker/dledger/DLedgerRoleChangeHandler.java index 09bf10c327d..75023ee1b8b 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/dledger/DLedgerRoleChangeHandler.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/dledger/DLedgerRoleChangeHandler.java @@ -22,45 +22,54 @@ import io.openmessaging.storage.dledger.utils.DLedgerUtils; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); - private ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactoryImpl("DLegerRoleChangeHandler_")); + 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 = Executors.newSingleThreadExecutor( + new ThreadFactoryImpl("DLegerRoleChangeHandler_", brokerController.getBrokerIdentity())); } - @Override public void handle(long term, MemberState.Role role) { + @Override + public void handle(long term, MemberState.Role role) { Runnable runnable = new Runnable() { - @Override public void run() { + @Override + public void run() { long start = System.currentTimeMillis(); try { boolean succ = true; - log.info("Begin handling broker role change term={} role={} currStoreRole={}", term, role, messageStore.getMessageStoreConfig().getBrokerRole()); + 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) { - brokerController.changeToSlave(dLedgerCommitLog.getId()); + changeToSlave(dLedgerCommitLog.getId()); } break; case FOLLOWER: - brokerController.changeToSlave(dLedgerCommitLog.getId()); + changeToSlave(dLedgerCommitLog.getId()); break; case LEADER: while (true) { @@ -68,10 +77,10 @@ public DLedgerRoleChangeHandler(BrokerController brokerController, DefaultMessag succ = false; break; } - if (dLegerServer.getdLedgerStore().getLedgerEndIndex() == -1) { + if (dLegerServer.getDLedgerStore().getLedgerEndIndex() == -1) { break; } - if (dLegerServer.getdLedgerStore().getLedgerEndIndex() == dLegerServer.getdLedgerStore().getCommittedIndex() + if (dLegerServer.getDLedgerStore().getLedgerEndIndex() == dLegerServer.getDLedgerStore().getCommittedIndex() && messageStore.dispatchBehindBytes() == 0) { break; } @@ -79,26 +88,101 @@ public DLedgerRoleChangeHandler(BrokerController brokerController, DefaultMessag } if (succ) { messageStore.recoverTopicQueueTable(); - brokerController.changeToMaster(BrokerRole.SYNC_MASTER); + changeToMaster(BrokerRole.SYNC_MASTER); } break; default: break; } - log.info("Finish handling broker role change succ={} term={} role={} currStoreRole={} cost={}", succ, term, role, messageStore.getMessageStoreConfig().getBrokerRole(), DLedgerUtils.elapsed(start)); + LOGGER.info("Finish handling broker role change succ={} term={} role={} currStoreRole={} cost={}", succ, term, role, messageStore.getMessageStoreConfig().getBrokerRole(), DLedgerUtils.elapsed(start)); } catch (Throwable t) { - log.info("[MONITOR]Failed handling broker role change term={} role={} currStoreRole={} cost={}", term, role, messageStore.getMessageStoreConfig().getBrokerRole(), DLedgerUtils.elapsed(start), 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); } - @Override public void startup() { + 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() { + @Override + public void shutdown() { executorService.shutdown(); } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/failover/EscapeBridge.java b/broker/src/main/java/org/apache/rocketmq/broker/failover/EscapeBridge.java new file mode 100644 index 00000000000..7c350fc1d7d --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/failover/EscapeBridge.java @@ -0,0 +1,356 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.StringUtils; +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.Pair; +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.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; + +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 = new ThreadPoolExecutor( + 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); + 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); + } + } + + private SendResult putMessageToRemoteBroker(MessageExtBrokerInner messageExt) { + 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 = topicPublishInfo.selectOneMessageQueue(); + + messageToPut.setQueueId(mqSelected.getQueueId()); + + final String brokerNameToSend = mqSelected.getBrokerName(); + final String brokerAddrToSend = this.brokerController.getTopicRouteInfoManager().findBrokerAddressInPublish(brokerNameToSend); + + 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(sendResult -> transformSendResult2PutResult(sendResult), 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); + } else 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 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); + final SendResult sendResult = this.brokerController.getBrokerOuterAPI().sendMessageToSpecificBroker( + brokerAddrToSend, brokerNameToSend, + messageExt, this.getProducerGroup(messageExt), SEND_TIMEOUT); + + 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 to specific queue failed, enableSlaveActingMaster={}, enableRemoteEscape={}.", + this.brokerController.getBrokerConfig().isEnableSlaveActingMaster(), this.brokerController.getBrokerConfig().isEnableRemoteEscape()); + return 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 Pair getMessage(String topic, long offset, int queueId, String brokerName, boolean deCompressBody) { + return getMessageAsync(topic, offset, queueId, brokerName, deCompressBody).join(); + } + + 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 new Pair<>(GetMessageStatus.MESSAGE_WAS_REMOVING, null); + } + List list = decodeMsgList(result, deCompressBody); + if (list == null || list.isEmpty()) { + LOG.warn("Can not get msg , topic {}, offset {}, queueId {}, result is {}", topic, offset, queueId, result); + return new Pair<>(result.getStatus(), null); + } + return new Pair<>(result.getStatus(), list.get(0)); + }); + } else { + return getMessageFromRemoteAsync(topic, offset, queueId, brokerName) + .thenApply(msg -> { + if (msg == null) { + return new Pair<>(GetMessageStatus.MESSAGE_WAS_REMOVING, null); + } + return new Pair<>(GetMessageStatus.FOUND, msg); + }); + } + } + + 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 MessageExt getMessageFromRemote(String topic, long offset, int queueId, String brokerName) { + return getMessageFromRemoteAsync(topic, offset, queueId, brokerName).join(); + } + + 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); + return CompletableFuture.completedFuture(null); + } + } + + return this.brokerController.getBrokerOuterAPI().pullMessageFromSpecificBrokerAsync(brokerName, + brokerAddr, this.innerConsumerGroupName, topic, queueId, offset, 1, DEFAULT_PULL_TIMEOUT_MILLIS) + .thenApply(pullResult -> { + if (pullResult.getPullStatus().equals(PullStatus.FOUND) && !pullResult.getMsgFoundList().isEmpty()) { + return pullResult.getMsgFoundList().get(0); + } + return null; + }); + } catch (Exception e) { + LOG.error("Get message from remote failed.", e); + } + + return CompletableFuture.completedFuture(null); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/filter/CommitLogDispatcherCalcBitMap.java b/broker/src/main/java/org/apache/rocketmq/broker/filter/CommitLogDispatcherCalcBitMap.java index d82c53fd90a..00f0c13dd3a 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/filter/CommitLogDispatcherCalcBitMap.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/filter/CommitLogDispatcherCalcBitMap.java @@ -20,8 +20,8 @@ import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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; @@ -34,7 +34,7 @@ */ public class CommitLogDispatcherCalcBitMap implements CommitLogDispatcher { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.FILTER_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.FILTER_LOGGER_NAME); protected final BrokerConfig brokerConfig; protected final ConsumerFilterManager consumerFilterManager; diff --git a/broker/src/main/java/org/apache/rocketmq/broker/filter/ConsumerFilterManager.java b/broker/src/main/java/org/apache/rocketmq/broker/filter/ConsumerFilterManager.java index 5f3b7eb7a6c..3a48f96b987 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/filter/ConsumerFilterManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/filter/ConsumerFilterManager.java @@ -17,38 +17,37 @@ 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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; -import org.apache.rocketmq.filter.FilterFactory; 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 java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.FILTER_LOGGER_NAME); + 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); + filterDataByTopic = new ConcurrentHashMap<>(256); private transient BrokerController brokerController; private transient BloomFilter bloomFilter; @@ -176,7 +175,7 @@ public ConsumerFilterData get(final String topic, final String consumerGroup) { } public Collection getByGroup(final String consumerGroup) { - Collection ret = new HashSet(); + Collection ret = new HashSet<>(); Iterator topicIterator = this.filterDataByTopic.values().iterator(); while (topicIterator.hasNext()) { @@ -324,7 +323,7 @@ public void setFilterDataByTopic(final ConcurrentHashMap - groupFilterData = new ConcurrentHashMap(); + groupFilterData = new ConcurrentHashMap<>(); private String topic; diff --git a/broker/src/main/java/org/apache/rocketmq/broker/filter/ExpressionForRetryMessageFilter.java b/broker/src/main/java/org/apache/rocketmq/broker/filter/ExpressionForRetryMessageFilter.java index 7f7da05dd02..d2d1087ef8a 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/filter/ExpressionForRetryMessageFilter.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/filter/ExpressionForRetryMessageFilter.java @@ -17,14 +17,13 @@ package org.apache.rocketmq.broker.filter; +import java.nio.ByteBuffer; +import java.util.Map; 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.common.protocol.heartbeat.SubscriptionData; - -import java.nio.ByteBuffer; -import java.util.Map; +import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; /** * Support filter to retry topic. diff --git a/broker/src/main/java/org/apache/rocketmq/broker/filter/ExpressionMessageFilter.java b/broker/src/main/java/org/apache/rocketmq/broker/filter/ExpressionMessageFilter.java index 0c90880b0f2..5d0340c3bce 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/filter/ExpressionMessageFilter.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/filter/ExpressionMessageFilter.java @@ -17,23 +17,22 @@ 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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; import org.apache.rocketmq.common.message.MessageDecoder; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; 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; -import java.nio.ByteBuffer; -import java.util.Map; - public class ExpressionMessageFilter implements MessageFilter { - protected static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.FILTER_LOGGER_NAME); + protected static final Logger log = LoggerFactory.getLogger(LoggerName.FILTER_LOGGER_NAME); protected final SubscriptionData subscriptionData; protected final ConsumerFilterData consumerFilterData; diff --git a/broker/src/main/java/org/apache/rocketmq/broker/filter/MessageEvaluationContext.java b/broker/src/main/java/org/apache/rocketmq/broker/filter/MessageEvaluationContext.java index 84921f0d657..980b738dda3 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/filter/MessageEvaluationContext.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/filter/MessageEvaluationContext.java @@ -48,7 +48,7 @@ public Map keyValues() { return null; } - Map copy = new HashMap(properties.size(), 1); + Map copy = new HashMap<>(properties.size(), 1); for (Entry entry : properties.entrySet()) { copy.put(entry.getKey(), entry.getValue()); diff --git a/broker/src/main/java/org/apache/rocketmq/broker/filtersrv/FilterServerManager.java b/broker/src/main/java/org/apache/rocketmq/broker/filtersrv/FilterServerManager.java deleted file mode 100644 index c1a860a912a..00000000000 --- a/broker/src/main/java/org/apache/rocketmq/broker/filtersrv/FilterServerManager.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.broker.filtersrv; - -import io.netty.channel.Channel; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; -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 org.apache.rocketmq.broker.BrokerController; -import org.apache.rocketmq.broker.BrokerStartup; -import org.apache.rocketmq.common.ThreadFactoryImpl; -import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.remoting.common.RemotingUtil; - -public class FilterServerManager { - - public static final long FILTER_SERVER_MAX_IDLE_TIME_MILLS = 30000; - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); - private final ConcurrentMap filterServerTable = - new ConcurrentHashMap(16); - private final BrokerController brokerController; - - private ScheduledExecutorService scheduledExecutorService = Executors - .newSingleThreadScheduledExecutor(new ThreadFactoryImpl("FilterServerManagerScheduledThread")); - - public FilterServerManager(final BrokerController brokerController) { - this.brokerController = brokerController; - } - - public void start() { - - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - FilterServerManager.this.createFilterServer(); - } catch (Exception e) { - log.error("", e); - } - } - }, 1000 * 5, 1000 * 30, TimeUnit.MILLISECONDS); - } - - public void createFilterServer() { - int more = - this.brokerController.getBrokerConfig().getFilterServerNums() - this.filterServerTable.size(); - String cmd = this.buildStartCommand(); - for (int i = 0; i < more; i++) { - FilterServerUtil.callShell(cmd, log); - } - } - - private String buildStartCommand() { - String config = ""; - if (BrokerStartup.configFile != null) { - config = String.format("-c %s", BrokerStartup.configFile); - } - - if (this.brokerController.getBrokerConfig().getNamesrvAddr() != null) { - config += String.format(" -n %s", this.brokerController.getBrokerConfig().getNamesrvAddr()); - } - - if (RemotingUtil.isWindowsPlatform()) { - return String.format("start /b %s\\bin\\mqfiltersrv.exe %s", - this.brokerController.getBrokerConfig().getRocketmqHome(), - config); - } else { - return String.format("sh %s/bin/startfsrv.sh %s", - this.brokerController.getBrokerConfig().getRocketmqHome(), - config); - } - } - - public void shutdown() { - this.scheduledExecutorService.shutdown(); - } - - public void registerFilterServer(final Channel channel, final String filterServerAddr) { - FilterServerInfo filterServerInfo = this.filterServerTable.get(channel); - if (filterServerInfo != null) { - filterServerInfo.setLastUpdateTimestamp(System.currentTimeMillis()); - } else { - filterServerInfo = new FilterServerInfo(); - filterServerInfo.setFilterServerAddr(filterServerAddr); - filterServerInfo.setLastUpdateTimestamp(System.currentTimeMillis()); - this.filterServerTable.put(channel, filterServerInfo); - log.info("Receive a New Filter Server<{}>", filterServerAddr); - } - } - - public void scanNotActiveChannel() { - - Iterator> it = this.filterServerTable.entrySet().iterator(); - while (it.hasNext()) { - Entry next = it.next(); - long timestamp = next.getValue().getLastUpdateTimestamp(); - Channel channel = next.getKey(); - if ((System.currentTimeMillis() - timestamp) > FILTER_SERVER_MAX_IDLE_TIME_MILLS) { - log.info("The Filter Server<{}> expired, remove it", next.getKey()); - it.remove(); - RemotingUtil.closeChannel(channel); - } - } - } - - public void doChannelCloseEvent(final String remoteAddr, final Channel channel) { - FilterServerInfo old = this.filterServerTable.remove(channel); - if (old != null) { - log.warn("The Filter Server<{}> connection<{}> closed, remove it", old.getFilterServerAddr(), - remoteAddr); - } - } - - public List buildNewFilterServerList() { - List addr = new ArrayList<>(); - Iterator> it = this.filterServerTable.entrySet().iterator(); - while (it.hasNext()) { - Entry next = it.next(); - addr.add(next.getValue().getFilterServerAddr()); - } - return addr; - } - - static class FilterServerInfo { - private String filterServerAddr; - private long lastUpdateTimestamp; - - public String getFilterServerAddr() { - return filterServerAddr; - } - - public void setFilterServerAddr(String filterServerAddr) { - this.filterServerAddr = filterServerAddr; - } - - public long getLastUpdateTimestamp() { - return lastUpdateTimestamp; - } - - public void setLastUpdateTimestamp(long lastUpdateTimestamp) { - this.lastUpdateTimestamp = lastUpdateTimestamp; - } - } -} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFastFailure.java b/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFastFailure.java index d176b86f7c2..d3d0bc8ba3a 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFastFailure.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFastFailure.java @@ -17,25 +17,35 @@ package org.apache.rocketmq.broker.latency; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.common.AbstractBrokerRunnable; import org.apache.rocketmq.common.ThreadFactoryImpl; +import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); - private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl( - "BrokerFastFailureScheduledThread")); + 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(); + public BrokerFastFailure(final BrokerController brokerController) { this.brokerController = brokerController; + this.scheduledExecutorService = new ScheduledThreadPoolExecutor(1, + new ThreadFactoryImpl("BrokerFastFailureScheduledThread", true, + brokerController == null ? null : brokerController.getBrokerConfig())); } public static RequestTask castRunnable(final Runnable runnable) { @@ -45,16 +55,16 @@ public static RequestTask castRunnable(final Runnable runnable) { return (RequestTask) object.getRunnable(); } } catch (Throwable e) { - log.error(String.format("castRunnable exception, %s", runnable.getClass().getName()), e); + LOGGER.error(String.format("castRunnable exception, %s", runnable.getClass().getName()), e); } return null; } public void start() { - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + this.scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.brokerController.getBrokerConfig()) { @Override - public void run() { + public void run0() { if (brokerController.getBrokerConfig().isBrokerFastFailureEnable()) { cleanExpiredRequest(); } @@ -63,6 +73,7 @@ public void run() { } private void cleanExpiredRequest() { + while (this.brokerController.getMessageStore().isOSPageCacheBusy()) { try { if (!this.brokerController.getSendThreadPoolQueue().isEmpty()) { @@ -72,7 +83,13 @@ private void cleanExpiredRequest() { } final RequestTask rt = castRunnable(runnable); - 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())); + 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; } @@ -86,11 +103,17 @@ private void cleanExpiredRequest() { cleanExpiredRequestInQueue(this.brokerController.getPullThreadPoolQueue(), this.brokerController.getBrokerConfig().getWaitTimeMillsInPullQueue()); + cleanExpiredRequestInQueue(this.brokerController.getLitePullThreadPoolQueue(), + this.brokerController.getBrokerConfig().getWaitTimeMillsInLitePullQueue()); + cleanExpiredRequestInQueue(this.brokerController.getHeartbeatThreadPoolQueue(), this.brokerController.getBrokerConfig().getWaitTimeMillsInHeartbeatQueue()); cleanExpiredRequestInQueue(this.brokerController.getEndTransactionThreadPoolQueue(), this .brokerController.getBrokerConfig().getWaitTimeMillsInTransactionQueue()); + + cleanExpiredRequestInQueue(this.brokerController.getAckThreadPoolQueue(), + brokerController.getBrokerConfig().getWaitTimeMillsInAckQueue()); } void cleanExpiredRequestInQueue(final BlockingQueue blockingQueue, final long maxWaitTimeMillsInQueue) { @@ -111,6 +134,10 @@ void cleanExpiredRequestInQueue(final BlockingQueue blockingQueue, fin 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; diff --git a/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFixedThreadPoolExecutor.java b/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFixedThreadPoolExecutor.java index 8060fd00c79..d2d1143a348 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFixedThreadPoolExecutor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFixedThreadPoolExecutor.java @@ -52,6 +52,6 @@ public BrokerFixedThreadPoolExecutor(final int corePoolSize, final int maximumPo @Override protected RunnableFuture newTaskFor(final Runnable runnable, final T value) { - return new FutureTaskExt(runnable, value); + return new FutureTaskExt<>(runnable, value); } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/loadbalance/MessageRequestModeManager.java b/broker/src/main/java/org/apache/rocketmq/broker/loadbalance/MessageRequestModeManager.java new file mode 100644 index 00000000000..0c69e2de94f --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/loadbalance/MessageRequestModeManager.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/LmqPullRequestHoldService.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/LmqPullRequestHoldService.java index 42b44b6ab37..88e74fd6e5a 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/LmqPullRequestHoldService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/LmqPullRequestHoldService.java @@ -19,12 +19,12 @@ import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class LmqPullRequestHoldService extends PullRequestHoldService { - private static final InternalLogger LOGGER = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); public LmqPullRequestHoldService(BrokerController brokerController) { super(brokerController); @@ -32,6 +32,9 @@ public LmqPullRequestHoldService(BrokerController brokerController) { @Override public String getServiceName() { + if (brokerController != null && brokerController.getBrokerConfig().isInBrokerContainer()) { + return this.brokerController.getBrokerIdentity().getIdentifier() + LmqPullRequestHoldService.class.getSimpleName(); + } return LmqPullRequestHoldService.class.getSimpleName(); } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/ManyPullRequest.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/ManyPullRequest.java index 170dae2939c..08703deaf0c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/ManyPullRequest.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/ManyPullRequest.java @@ -43,4 +43,8 @@ public synchronized List cloneListAndClear() { public ArrayList getPullRequestList() { return pullRequestList; } + + public synchronized boolean isEmpty() { + return this.pullRequestList.isEmpty(); + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotificationRequest.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotificationRequest.java new file mode 100644 index 00000000000..2ff9a73a4bb --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotificationRequest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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(); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotifyMessageArrivingListener.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotifyMessageArrivingListener.java index ff0901126f2..3c099fe2f40 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotifyMessageArrivingListener.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotifyMessageArrivingListener.java @@ -17,21 +17,29 @@ package org.apache.rocketmq.broker.longpolling; +import org.apache.rocketmq.broker.processor.NotificationProcessor; +import org.apache.rocketmq.broker.processor.PopMessageProcessor; import org.apache.rocketmq.store.MessageArrivingListener; import java.util.Map; public class NotifyMessageArrivingListener implements MessageArrivingListener { private final PullRequestHoldService pullRequestHoldService; + private final PopMessageProcessor popMessageProcessor; + private final NotificationProcessor notificationProcessor; - public NotifyMessageArrivingListener(final PullRequestHoldService pullRequestHoldService) { + public NotifyMessageArrivingListener(final PullRequestHoldService pullRequestHoldService, final PopMessageProcessor popMessageProcessor, final NotificationProcessor notificationProcessor) { this.pullRequestHoldService = pullRequestHoldService; + this.popMessageProcessor = popMessageProcessor; + this.notificationProcessor = notificationProcessor; } @Override public void arriving(String topic, int queueId, long logicOffset, long tagsCode, - long msgStoreTime, byte[] filterBitMap, Map properties) { + long msgStoreTime, byte[] filterBitMap, Map properties) { this.pullRequestHoldService.notifyMessageArriving(topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties); + this.popMessageProcessor.notifyMessageArriving(topic, queueId); + this.notificationProcessor.notifyMessageArriving(topic, queueId); } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PollingHeader.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PollingHeader.java new file mode 100644 index 00000000000..9f6774a0f3c --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PollingHeader.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PollingResult.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PollingResult.java new file mode 100644 index 00000000000..6b7c4fa4a89 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PollingResult.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopLongPollingService.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopLongPollingService.java new file mode 100644 index 00000000000..113c91297e4 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopLongPollingService.java @@ -0,0 +1,333 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.atomic.AtomicLong; +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.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; + +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 ConcurrentHashMap> topicCidMap; + private final ConcurrentLinkedHashMap> pollingMap; + private long lastCleanTime = 0; + + private final AtomicLong totalPollingNum = new AtomicLong(0); + + public PopLongPollingService(BrokerController brokerController, NettyRequestProcessor processor) { + this.brokerController = brokerController; + this.processor = processor; + // 100000 topic default, 100000 lru topic + cid + qid + this.topicCidMap = new ConcurrentHashMap<>(brokerController.getBrokerConfig().getPopPollingMapSize()); + this.pollingMap = new ConcurrentLinkedHashMap.Builder>() + .maximumWeightedCapacity(this.brokerController.getBrokerConfig().getPopPollingMapSize()).build(); + } + + @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.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 { + 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.size(), 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.entrySet()) { + ConcurrentSkipListSet popQ = entry.getValue(); + PopRequest first; + while ((first = popQ.pollFirst()) != null) { + wakeUp(first); + } + } + } catch (Throwable e) { + } + } + + public void notifyMessageArriving(final String topic, final int queueId) { + ConcurrentHashMap cids = topicCidMap.get(topic); + if (cids == null) { + return; + } + for (Map.Entry cid : cids.entrySet()) { + if (queueId >= 0) { + notifyMessageArriving(topic, cid.getKey(), -1); + } + notifyMessageArriving(topic, cid.getKey(), queueId); + } + } + + public boolean notifyMessageArriving(final String topic, final String cid, final int queueId) { + ConcurrentSkipListSet remotingCommands = pollingMap.get(KeyBuilder.buildPollingKey(topic, cid, queueId)); + if (remotingCommands == null || remotingCommands.isEmpty()) { + return false; + } + PopRequest popRequest = remotingCommands.pollFirst(); + //clean inactive channel + while (popRequest != null && !popRequest.getChannel().isActive()) { + totalPollingNum.decrementAndGet(); + popRequest = remotingCommands.pollFirst(); + } + + if (popRequest == null) { + return false; + } + totalPollingNum.decrementAndGet(); + if (brokerController.getBrokerConfig().isEnablePopLog()) { + POP_LOGGER.info("lock release , new msg arrive , 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()) { + POP_LOGGER.error("ProcessRequestWrapper response to {} failed", request.getChannel().remoteAddress(), future.cause()); + POP_LOGGER.error(request.toString()); + POP_LOGGER.error(response.toString()); + } + }); + } + } 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) { + if (requestHeader.getPollTime() <= 0 || this.isStopped()) { + return NOT_POLLING; + } + ConcurrentHashMap cids = topicCidMap.get(requestHeader.getTopic()); + if (cids == null) { + cids = new ConcurrentHashMap<>(); + ConcurrentHashMap old = topicCidMap.putIfAbsent(requestHeader.getTopic(), cids); + if (old != null) { + cids = old; + } + } + cids.putIfAbsent(requestHeader.getConsumerGroup(), Byte.MIN_VALUE); + long expired = requestHeader.getBornTime() + requestHeader.getPollTime(); + final PopRequest request = new PopRequest(remotingCommand, ctx, expired); + 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); + if (queue == null) { + queue = new ConcurrentSkipListSet<>(PopRequest.COMPARATOR); + ConcurrentSkipListSet old = pollingMap.putIfAbsent(key, queue); + if (old != null) { + queue = old; + } + } else { + // check size + 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 ConcurrentLinkedHashMap> getPollingMap() { + return pollingMap; + } + + private void cleanUnusedResource() { + try { + { + Iterator>> topicCidMapIter = topicCidMap.entrySet().iterator(); + while (topicCidMapIter.hasNext()) { + Map.Entry> entry = topicCidMapIter.next(); + String topic = entry.getKey(); + if (brokerController.getTopicConfigManager().selectTopicConfig(topic) == null) { + POP_LOGGER.info("remove not exit 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().getSubscriptionGroupTable().containsKey(cid)) { + POP_LOGGER.info("remove not exit sub {} of topic {} in topicCidMap!", cid, topic); + cidMapIter.remove(); + } + } + } + } + + { + Iterator>> pollingMapIter = pollingMap.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 not exit topic {} in pollingMap!", topic); + pollingMapIter.remove(); + continue; + } + if (!brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().containsKey(cid)) { + POP_LOGGER.info("remove not exit sub {} of topic {} in pollingMap!", cid, topic); + pollingMapIter.remove(); + } + } + } + } catch (Throwable e) { + POP_LOGGER.error("cleanUnusedResource", e); + } + + lastCleanTime = System.currentTimeMillis(); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopRequest.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopRequest.java new file mode 100644 index 00000000000..a45bcce9f60 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopRequest.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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 io.netty.channel.Channel; + +public class PopRequest { + private static final AtomicLong COUNTER = new AtomicLong(Long.MIN_VALUE); + + private final RemotingCommand remotingCommand; + private final ChannelHandlerContext ctx; + private final long expired; + private final AtomicBoolean complete = new AtomicBoolean(false); + private final long op = COUNTER.getAndIncrement(); + + public PopRequest(RemotingCommand remotingCommand, ChannelHandlerContext ctx, long expired) { + this.ctx = ctx; + this.remotingCommand = remotingCommand; + this.expired = expired; + } + + 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; + } + + @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; + }; +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PullRequest.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PullRequest.java index 045ab9b16a2..5e47105579d 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PullRequest.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PullRequest.java @@ -17,8 +17,8 @@ package org.apache.rocketmq.broker.longpolling; import io.netty.channel.Channel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.remoting.protocol.RemotingCommand; +import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.store.MessageFilter; public class PullRequest { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PullRequestHoldService.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PullRequestHoldService.java index 85ca9f73b4c..e8da9d0c47c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PullRequestHoldService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PullRequestHoldService.java @@ -25,17 +25,17 @@ import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.SystemClock; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.ConsumeQueueExt; public class PullRequestHoldService extends ServiceThread { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + 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); + new ConcurrentHashMap<>(1024); public PullRequestHoldService(final BrokerController brokerController) { this.brokerController = brokerController; @@ -52,6 +52,7 @@ public void suspendPullRequest(final String topic, final int queueId, final Pull } } + pullRequest.getRequestCommand().setSuspended(true); mpr.addPullRequest(pullRequest); } @@ -78,7 +79,7 @@ public void run() { this.checkHoldRequest(); long costTime = this.systemClock.now() - beginLockTimestamp; if (costTime > 5 * 1000) { - log.info("[NOTIFYME] check hold request cost {} ms.", costTime); + log.warn("PullRequestHoldService: check hold pull request cost {}ms", costTime); } } catch (Throwable e) { log.warn(this.getServiceName() + " service has exception. ", e); @@ -90,6 +91,9 @@ public void run() { @Override public String getServiceName() { + if (brokerController != null && brokerController.getBrokerConfig().isInBrokerContainer()) { + return this.brokerController.getBrokerIdentity().getIdentifier() + PullRequestHoldService.class.getSimpleName(); + } return PullRequestHoldService.class.getSimpleName(); } @@ -103,7 +107,9 @@ protected void checkHoldRequest() { try { this.notifyMessageArriving(topic, queueId, offset); } catch (Throwable e) { - log.error("check hold request failed. topic={}, queueId={}", topic, queueId, e); + log.error( + "PullRequestHoldService: failed to check hold request failed, topic={}, queueId={}", topic, + queueId, e); } } } @@ -120,7 +126,7 @@ public void notifyMessageArriving(final String topic, final int queueId, final l if (mpr != null) { List requestList = mpr.cloneListAndClear(); if (requestList != null) { - List replayList = new ArrayList(); + List replayList = new ArrayList<>(); for (PullRequest request : requestList) { long newestOffset = maxOffset; @@ -141,7 +147,9 @@ public void notifyMessageArriving(final String topic, final int queueId, final l this.brokerController.getPullMessageProcessor().executeRequestWhenWakeup(request.getClientChannel(), request.getRequestCommand()); } catch (Throwable e) { - log.error("execute request when wakeup failed.", e); + log.error( + "PullRequestHoldService#notifyMessageArriving: failed to execute request when " + + "message matched, topic={}, queueId={}", topic, queueId, e); } continue; } @@ -152,7 +160,9 @@ public void notifyMessageArriving(final String topic, final int queueId, final l this.brokerController.getPullMessageProcessor().executeRequestWhenWakeup(request.getClientChannel(), request.getRequestCommand()); } catch (Throwable e) { - log.error("execute request when wakeup failed.", e); + log.error( + "PullRequestHoldService#notifyMessageArriving: failed to execute request when time's " + + "up, topic={}, queueId={}", topic, queueId, e); } continue; } @@ -166,4 +176,22 @@ public void notifyMessageArriving(final String topic, final int queueId, final l } } } + + 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); + } + } + } + + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsConstant.java b/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsConstant.java new file mode 100644 index 00000000000..73b40f6ba59 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsConstant.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 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 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 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_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"; +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManager.java b/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManager.java new file mode 100644 index 00000000000..f0b76107ec4 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManager.java @@ -0,0 +1,559 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.LoggingMetricExporter; +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.PeriodicMetricReader; +import io.opentelemetry.sdk.resources.Resource; +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 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.slf4j.bridge.SLF4JBridgeHandler; + +import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.AGGREGATION_DELTA; +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_THROUGHPUT_IN_TOTAL; +import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.COUNTER_THROUGHPUT_OUT_TOTAL; +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_PROCESSOR_WATERMARK; +import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.GAUGE_PRODUCER_CONNECTIONS; +import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.HISTOGRAM_MESSAGE_SIZE; +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 static Map LABEL_MAP = new HashMap<>(); + private OtlpGrpcMetricExporter metricExporter; + private PeriodicMetricReader periodicMetricReader; + private PrometheusHttpServer prometheusHttpServer; + private LoggingMetricExporter loggingMetricExporter; + private Meter brokerMeter; + + public static Supplier attributesBuilderSupplier = Attributes::builder; + + // broker stats metrics + public static ObservableLongGauge processorWatermark = new NopObservableLongGauge(); + public static ObservableLongGauge brokerPermission = new NopObservableLongGauge(); + + // request metrics + public static LongCounter messagesInTotal = new NopLongCounter(); + public static LongCounter messagesOutTotal = new NopLongCounter(); + public static LongCounter throughputInTotal = new NopLongCounter(); + public static LongCounter throughputOutTotal = new NopLongCounter(); + public static LongHistogram messageSize = new NopLongHistogram(); + + // client connection metrics + public static ObservableLongGauge producerConnection = new NopObservableLongGauge(); + public static ObservableLongGauge consumerConnection = new NopObservableLongGauge(); + + // Lag metrics + public static ObservableLongGauge consumerLagMessages = new NopObservableLongGauge(); + public static ObservableLongGauge consumerLagLatency = new NopObservableLongGauge(); + public static ObservableLongGauge consumerInflightMessages = new NopObservableLongGauge(); + public static ObservableLongGauge consumerQueueingLatency = new NopObservableLongGauge(); + public static ObservableLongGauge consumerReadyMessages = new NopObservableLongGauge(); + public static LongCounter sendToDlqMessages = new NopLongCounter(); + + 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); + init(); + } + + public static AttributesBuilder newAttributesBuilder() { + AttributesBuilder attributesBuilder; + if (attributesBuilderSupplier == null) { + attributesBuilderSupplier = Attributes::builder; + } + attributesBuilder = attributesBuilderSupplier.get(); + LABEL_MAP.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) { + topicMessageType = TopicMessageType.DELAY; + } + return topicMessageType; + } + + public Meter getBrokerMeter() { + return brokerMeter; + } + + 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; + } + LABEL_MAP.put(split[0], split[1]); + } + } + if (brokerConfig.isMetricsInDelta()) { + LABEL_MAP.put(LABEL_AGGREGATION, AGGREGATION_DELTA); + } + LABEL_MAP.put(LABEL_NODE_TYPE, NODE_TYPE_BROKER); + LABEL_MAP.put(LABEL_CLUSTER_NAME, brokerConfig.getBrokerClusterName()); + LABEL_MAP.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 = LoggingMetricExporter.create(brokerConfig.isMetricsInDelta() ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE); + java.util.logging.Logger.getLogger(LoggingMetricExporter.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(); + 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 + ); + InstrumentSelector messageSizeSelector = InstrumentSelector.builder() + .setType(InstrumentType.HISTOGRAM) + .setName(HISTOGRAM_MESSAGE_SIZE) + .build(); + View messageSizeView = View.builder() + .setAggregation(Aggregation.explicitBucketHistogram(messageSizeBuckets)) + .build(); + providerBuilder.registerView(messageSizeSelector, messageSizeView); + + for (Pair selectorViewPair : RemotingMetricsManager.getMetricsView()) { + providerBuilder.registerView(selectorViewPair.getObject1(), selectorViewPair.getObject2()); + } + + for (Pair selectorViewPair : messageStore.getMetricsView()) { + providerBuilder.registerView(selectorViewPair.getObject1(), selectorViewPair.getObject2()); + } + + for (Pair selectorViewPair : PopMetricsManager.getMetricsView()) { + providerBuilder.registerView(selectorViewPair.getObject1(), selectorViewPair.getObject2()); + } + } + + private void initStatsMetrics() { + 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())); + } + + private void initRequestMetrics() { + 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(); + } + + private void initConnectionMetrics() { + 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() { + consumerLagMessages = brokerMeter.gaugeBuilder(GAUGE_CONSUMER_LAG_MESSAGES) + .setDescription("Consumer lag messages") + .ofLongs() + .buildWithCallback(measurement -> + consumerLagCalculator.calculateLag(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(result -> { + long latency = 0; + long curTimeStamp = System.currentTimeMillis(); + if (result.earliestUnconsumedTimestamp != 0) { + latency = curTimeStamp - result.earliestUnconsumedTimestamp; + } + measurement.record(latency, buildLagAttributes(result)); + })); + + 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)))); + + sendToDlqMessages = brokerMeter.counterBuilder(COUNTER_CONSUMER_SEND_TO_DLQ_MESSAGES_TOTAL) + .setDescription("Consumer send to DLQ messages") + .build(); + } + + private void initOtherMetrics() { + RemotingMetricsManager.initMetrics(brokerMeter, BrokerMetricsManager::newAttributesBuilder); + messageStore.initMetrics(brokerMeter, BrokerMetricsManager::newAttributesBuilder); + PopMetricsManager.initMetrics(brokerMeter, brokerController, BrokerMetricsManager::newAttributesBuilder); + } + + public void shutdown() { + 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(); + } + } + +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/metrics/ConsumerAttr.java b/broker/src/main/java/org/apache/rocketmq/broker/metrics/ConsumerAttr.java new file mode 100644 index 00000000000..28f36ccd3fa --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/metrics/ConsumerAttr.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/metrics/ConsumerLagCalculator.java b/broker/src/main/java/org/apache/rocketmq/broker/metrics/ConsumerLagCalculator.java new file mode 100644 index 00000000000..7a5f1f765e5 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/metrics/ConsumerLagCalculator.java @@ -0,0 +1,470 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +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.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.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; + +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 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.popInflightMessageCounter = brokerController.getPopInflightMessageCounter(); + } + + private 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 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(); + ConsumerGroupInfo consumerGroupInfo = consumerManager.getConsumerGroupInfo(group, true); + boolean isPop = false; + if (consumerGroupInfo != null) { + isPop = consumerGroupInfo.getConsumeType() == ConsumeType.CONSUME_POP; + } + Set topics; + if (brokerConfig.isUseStaticSubscription()) { + SubscriptionGroupConfig subscriptionGroupConfig = subscriptionEntry.getValue(); + 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); + 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; + } + } + consumer.accept(new ProcessGroupInfo(group, topic, true, null)); + } else { + consumer.accept(new ProcessGroupInfo(group, topic, false, null)); + } + } + } + } + + public void calculateLag(Consumer lagRecorder) { + processAllGroup(info -> { + if (info.group == null || info.topic == null) { + return; + } + + CalculateLagResult result = new CalculateLagResult(info.group, info.topic, false); + + Pair lag = getConsumerLagStats(info.group, info.topic, info.isPop); + if (lag != null) { + result.lag = lag.getObject1(); + result.earliestUnconsumedTimestamp = lag.getObject2(); + } + lagRecorder.accept(result); + + if (info.isPop) { + 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); + } + }); + } + + public void calculateInflight(Consumer inflightRecorder) { + processAllGroup(info -> { + CalculateInflightResult result = new CalculateInflightResult(info.group, info.topic, false); + Pair inFlight = getInFlightMsgStats(info.group, info.topic, info.isPop); + if (inFlight != null) { + result.inFlight = inFlight.getObject1(); + result.earliestUnPulledTimestamp = inFlight.getObject2(); + } + inflightRecorder.accept(result); + + if (info.isPop) { + 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); + } + }); + } + + public void calculateAvailable(Consumer availableRecorder) { + processAllGroup(info -> { + CalculateAvailableResult result = new CalculateAvailableResult(info.group, info.topic, false); + + result.available = getAvailableMsgCount(info.group, info.topic, info.isPop); + availableRecorder.accept(result); + + if (info.isPop) { + long retryAvailable = getAvailableMsgCount(info.group, info.retryTopic, true); + + result = new CalculateAvailableResult(info.group, info.topic, true); + result.available = retryAvailable; + availableRecorder.accept(result); + } + }); + } + + public Pair getConsumerLagStats(String group, String topic, boolean isPop) { + 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; + } + + return new Pair<>(total, earliestUnconsumedTimestamp); + } + + public Pair getConsumerLagStats(String group, String topic, int queueId, boolean isPop) { + long brokerOffset = messageStore.getMaxOffsetInQueue(topic, queueId); + if (brokerOffset < 0) { + brokerOffset = 0; + } + + if (isPop) { + 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) { + 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) { + if (isPop) { + 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) { + 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) { + long brokerOffset = messageStore.getMaxOffsetInQueue(topic, queueId); + if (brokerOffset < 0) { + brokerOffset = 0; + } + + long pullOffset; + if (isPop) { + pullOffset = popBufferMergeService.getLatestOffset(topic, group, queueId); + if (pullOffset < 0) { + pullOffset = offsetManager.queryOffset(group, topic, queueId); + } + if (pullOffset < 0) { + pullOffset = brokerOffset; + } + } 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())) { + subscriptionData = new SubscriptionData(); + subscriptionData.setTopic(simpleSubscriptionData.getTopic()); + subscriptionData.setExpressionType(simpleSubscriptionData.getExpressionType()); + subscriptionData.setSubString(simpleSubscriptionData.getExpression()); + 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; + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopMetricsConstant.java b/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopMetricsConstant.java new file mode 100644 index 00000000000..41917ed5066 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopMetricsConstant.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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"; +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopMetricsManager.java b/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopMetricsManager.java new file mode 100644 index 00000000000..463371d7e8b --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopMetricsManager.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 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.pop.AckMsg; +import org.apache.rocketmq.store.pop.PopCheckPoint; + +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 { + public static Supplier attributesBuilderSupplier; + + private static LongHistogram popBufferScanTimeConsume = new NopLongHistogram(); + private static LongCounter popRevivePutTotal = new NopLongCounter(); + private static LongCounter popReviveGetTotal = new NopLongCounter(); + private static LongCounter popReviveRetryMessageTotal = new NopLongCounter(); + + public static 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(); + View popBufferScanTimeConsumeView = View.builder() + .setAggregation(Aggregation.explicitBucketHistogram(rpcCostTimeBuckets)) + .build(); + return Lists.newArrayList(new Pair<>(popBufferScanTimeConsumeSelector, popBufferScanTimeConsumeView)); + } + + public static void initMetrics(Meter meter, BrokerController brokerController, + Supplier attributesBuilderSupplier) { + PopMetricsManager.attributesBuilderSupplier = attributesBuilderSupplier; + + popBufferScanTimeConsume = meter.histogramBuilder(HISTOGRAM_POP_BUFFER_SCAN_TIME_CONSUME) + .setDescription("Time consuming of pop buffer scan") + .setUnit("milliseconds") + .ofLongs() + .build(); + popRevivePutTotal = meter.counterBuilder(COUNTER_POP_REVIVE_IN_MESSAGE_TOTAL) + .setDescription("Total number of put message to revive topic") + .build(); + popReviveGetTotal = meter.counterBuilder(COUNTER_POP_REVIVE_OUT_MESSAGE_TOTAL) + .setDescription("Total number of get message from revive topic") + .build(); + 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 static void calculatePopBufferOffsetSize(BrokerController brokerController, + ObservableLongMeasurement measurement) { + PopBufferMergeService popBufferMergeService = brokerController.getPopMessageProcessor().getPopBufferMergeService(); + measurement.record(popBufferMergeService.getOffsetTotalSize(), newAttributesBuilder().build()); + } + + private static void calculatePopBufferCkSize(BrokerController brokerController, + ObservableLongMeasurement measurement) { + PopBufferMergeService popBufferMergeService = brokerController.getPopMessageProcessor().getPopBufferMergeService(); + measurement.record(popBufferMergeService.getBufferedCKSize(), newAttributesBuilder().build()); + } + + private static void calculatePopReviveLatency(BrokerController brokerController, + ObservableLongMeasurement measurement) { + PopReviveService[] popReviveServices = brokerController.getAckMessageProcessor().getPopReviveServices(); + for (PopReviveService popReviveService : popReviveServices) { + measurement.record(popReviveService.getReviveBehindMillis(), newAttributesBuilder() + .put(LABEL_QUEUE_ID, popReviveService.getQueueId()) + .build()); + } + } + + private static void calculatePopReviveLag(BrokerController brokerController, + ObservableLongMeasurement measurement) { + PopReviveService[] popReviveServices = brokerController.getAckMessageProcessor().getPopReviveServices(); + for (PopReviveService popReviveService : popReviveServices) { + measurement.record(popReviveService.getReviveBehindMessages(), newAttributesBuilder() + .put(LABEL_QUEUE_ID, popReviveService.getQueueId()) + .build()); + } + } + + public static void incPopReviveAckPutCount(AckMsg ackMsg, PutMessageStatus status) { + incPopRevivePutCount(ackMsg.getConsumerGroup(), ackMsg.getTopic(), PopReviveMessageType.ACK, status, 1); + } + + public static void incPopReviveCkPutCount(PopCheckPoint checkPoint, PutMessageStatus status) { + incPopRevivePutCount(checkPoint.getCId(), checkPoint.getTopic(), PopReviveMessageType.CK, status, 1); + } + + public static void incPopRevivePutCount(String group, String topic, PopReviveMessageType messageType, + PutMessageStatus status, int num) { + Attributes attributes = newAttributesBuilder() + .put(LABEL_CONSUMER_GROUP, group) + .put(LABEL_TOPIC, topic) + .put(LABEL_REVIVE_MESSAGE_TYPE, messageType.name()) + .put(LABEL_PUT_STATUS, status.name()) + .build(); + popRevivePutTotal.add(num, attributes); + } + + public static void incPopReviveAckGetCount(AckMsg ackMsg, int queueId) { + incPopReviveGetCount(ackMsg.getConsumerGroup(), ackMsg.getTopic(), PopReviveMessageType.ACK, queueId, 1); + } + + public static void incPopReviveCkGetCount(PopCheckPoint checkPoint, int queueId) { + incPopReviveGetCount(checkPoint.getCId(), checkPoint.getTopic(), PopReviveMessageType.CK, queueId, 1); + } + + public static void incPopReviveGetCount(String group, String topic, PopReviveMessageType messageType, int queueId, + int num) { + AttributesBuilder builder = 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(); + popReviveGetTotal.add(num, attributes); + } + + public static void incPopReviveRetryMessageCount(PopCheckPoint checkPoint, PutMessageStatus status) { + AttributesBuilder builder = newAttributesBuilder(); + Attributes attributes = builder + .put(LABEL_CONSUMER_GROUP, checkPoint.getCId()) + .put(LABEL_TOPIC, checkPoint.getTopic()) + .put(LABEL_PUT_STATUS, status.name()) + .build(); + popReviveRetryMessageTotal.add(1, attributes); + } + + public static void recordPopBufferScanTimeConsume(long time) { + popBufferScanTimeConsume.record(time, newAttributesBuilder().build()); + } + + public static AttributesBuilder newAttributesBuilder() { + return attributesBuilderSupplier != null ? attributesBuilderSupplier.get() : Attributes.builder(); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopReviveMessageType.java b/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopReviveMessageType.java new file mode 100644 index 00000000000..3f6fe9c4750 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopReviveMessageType.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/metrics/ProducerAttr.java b/broker/src/main/java/org/apache/rocketmq/broker/metrics/ProducerAttr.java new file mode 100644 index 00000000000..d40aba2501a --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/metrics/ProducerAttr.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/mqtrace/ConsumeMessageContext.java b/broker/src/main/java/org/apache/rocketmq/broker/mqtrace/ConsumeMessageContext.java index ae5d0770899..ed7bfba06d6 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/mqtrace/ConsumeMessageContext.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/mqtrace/ConsumeMessageContext.java @@ -17,6 +17,8 @@ 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 { @@ -30,13 +32,22 @@ public class ConsumeMessageContext { 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 String namespace; + private String namespace; public String getConsumerGroup() { return consumerGroup; } @@ -109,6 +120,14 @@ 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; } @@ -117,6 +136,62 @@ 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; } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/mqtrace/SendMessageContext.java b/broker/src/main/java/org/apache/rocketmq/broker/mqtrace/SendMessageContext.java index ab6452e593c..aaa84b720c1 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/mqtrace/SendMessageContext.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/mqtrace/SendMessageContext.java @@ -17,11 +17,16 @@ 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; @@ -38,14 +43,37 @@ public class SendMessageContext { 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; - private String namespace; + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } public boolean isSuccess() { return isSuccess; @@ -79,6 +107,14 @@ 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; } @@ -207,10 +243,66 @@ 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; } @@ -230,12 +322,4 @@ public int getCommercialSendTimes() { public void setCommercialSendTimes(final int commercialSendTimes) { this.commercialSendTimes = commercialSendTimes; } - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/offset/BroadcastOffsetManager.java b/broker/src/main/java/org/apache/rocketmq/broker/offset/BroadcastOffsetManager.java new file mode 100644 index 00000000000..9896735dd1c --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/offset/BroadcastOffsetManager.java @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +/** + * 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) { + + 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); + broadcastOffsetData.clientOffsetStore.compute(clientId, (clientIdK, offsetStore) -> { + if (offsetStore == null) { + offsetStore = new BroadcastTimedOffsetStore(fromProxy); + } + + 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)); + return offsetStore; + } + + if (offsetStore.fromProxy == fromProxy) { + return offsetStore; + } + + offset.set(getOffset(offsetStore, topic, groupId, queueId)); + return offsetStore; + }); + return offset.get(); + } + + private long getOffset(BroadcastTimedOffsetStore offsetStore, String topic, String groupId, int queueId) { + 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(); + } + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/offset/BroadcastOffsetStore.java b/broker/src/main/java/org/apache/rocketmq/broker/offset/BroadcastOffsetStore.java new file mode 100644 index 00000000000..3770e576ac8 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/offset/BroadcastOffsetStore.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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(); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManager.java b/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManager.java index f09522a7bd7..8bf4e9a5994 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManager.java @@ -16,6 +16,7 @@ */ package org.apache.rocketmq.broker.offset; +import com.google.common.base.Strings; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -24,24 +25,36 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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 { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); - protected static final String TOPIC_GROUP_SEPARATOR = "@"; + private static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + public static final String TOPIC_GROUP_SEPARATOR = "@"; - protected ConcurrentMap> offsetTable = - new ConcurrentHashMap>(512); + private DataVersion dataVersion = new DataVersion(); + + private ConcurrentMap> offsetTable = + new ConcurrentHashMap<>(512); + + private final ConcurrentMap> resetOffsetTable = + new ConcurrentHashMap<>(512); + + private final ConcurrentMap> pullOffsetTable = + new ConcurrentHashMap<>(512); protected transient BrokerController brokerController; + private final transient AtomicLong versionChangeCounter = new AtomicLong(0); + public ConsumerOffsetManager() { } @@ -49,6 +62,36 @@ public ConsumerOffsetManager(BrokerController brokerController) { this.brokerController = brokerController; } + 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(); + 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(); + LOG.warn("Clean topic's offset, {}, {}", topicAtGroup, next.getValue()); + } + } + } + } + public void scanUnsubscribedTopic() { Iterator>> it = this.offsetTable.entrySet().iterator(); while (it.hasNext()) { @@ -62,7 +105,7 @@ public void scanUnsubscribedTopic() { if (null == brokerController.getConsumerManager().findSubscriptionData(group, topic) && this.offsetBehindMuchThanData(topic, next.getValue())) { it.remove(); - log.warn("remove topic offset, {}", topicAtGroup); + LOG.warn("remove topic offset, {}", topicAtGroup); } } } @@ -83,7 +126,7 @@ private boolean offsetBehindMuchThanData(final String topic, ConcurrentMap whichTopicByConsumer(final String group) { - Set topics = new HashSet(); + Set topics = new HashSet<>(); Iterator>> it = this.offsetTable.entrySet().iterator(); while (it.hasNext()) { @@ -101,7 +144,7 @@ public Set whichTopicByConsumer(final String group) { } public Set whichGroupByTopic(final String topic) { - Set groups = new HashSet(); + Set groups = new HashSet<>(); Iterator>> it = this.offsetTable.entrySet().iterator(); while (it.hasNext()) { @@ -118,6 +161,28 @@ public Set whichGroupByTopic(final String topic) { 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 @@ -128,30 +193,85 @@ public void commitOffset(final String clientHost, final String group, final Stri 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(32); + map = new ConcurrentHashMap<>(32); 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); + 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) { + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); + } } + 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) + if (offset != null) { return offset; + } } - return -1; + 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; + } + + @Override public String encode() { return this.encode(false); } @@ -166,11 +286,13 @@ public void decode(String jsonString) { if (jsonString != null) { ConsumerOffsetManager obj = RemotingSerializable.fromJson(jsonString, ConsumerOffsetManager.class); if (obj != null) { - this.offsetTable = obj.offsetTable; + this.setOffsetTable(obj.getOffsetTable()); + this.dataVersion = obj.dataVersion; } } } + @Override public String encode(final boolean prettyFormat) { return RemotingSerializable.toJson(this, prettyFormat); } @@ -179,13 +301,13 @@ public ConcurrentMap> getOffsetTable() { return offsetTable; } - public void setOffsetTable(ConcurrentHashMap> offsetTable) { + public void setOffsetTable(ConcurrentMap> offsetTable) { this.offsetTable = offsetTable; } public Map queryMinOffsetInAllGroup(final String topic, final String filterGroups) { - Map queueMinOffset = new HashMap(); + Map queueMinOffset = new HashMap<>(); Set topicGroups = this.offsetTable.keySet(); if (!UtilAll.isBlank(filterGroups)) { for (String group : filterGroups.split(",")) { @@ -228,10 +350,18 @@ public Map queryOffset(final String group, final String topic) { 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)); + this.offsetTable.put(topic + TOPIC_GROUP_SEPARATOR + destGroup, new ConcurrentHashMap<>(offsets)); } } + public DataVersion getDataVersion() { + return dataVersion; + } + + public void setDataVersion(DataVersion dataVersion) { + this.dataVersion = dataVersion; + } + public void removeOffset(final String group) { Iterator>> it = this.offsetTable.entrySet().iterator(); while (it.hasNext()) { @@ -241,11 +371,59 @@ public void removeOffset(final String group) { String[] arrays = topicAtGroup.split(TOPIC_GROUP_SEPARATOR); if (arrays.length == 2 && group.equals(arrays[1])) { it.remove(); - log.warn("clean group offset {}", topicAtGroup); + LOG.warn("clean group offset {}", topicAtGroup); } } } + } + + 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; + 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); + 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. + ConcurrentMap currentOffsetMap = offsetTable.get(key); + if (null != currentOffsetMap) { + currentOffsetMap.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); + } + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoLockManager.java b/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoLockManager.java new file mode 100644 index 00000000000..37b3eed2302 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoLockManager.java @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; + +public class ConsumerOrderInfoLockManager { + private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); + private final BrokerController brokerController; + private final Map timeoutMap = new ConcurrentHashMap<>(); + private final Timer timer; + private static final int TIMER_TICK_MS = 100; + + public ConsumerOrderInfoLockManager(BrokerController brokerController) { + this.brokerController = brokerController; + this.timer = new HashedWheelTimer( + new ThreadFactoryImpl("ConsumerOrderInfoLockManager_"), + TIMER_TICK_MS, TimeUnit.MILLISECONDS); + } + + /** + * when ConsumerOrderInfoManager 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 = ConsumerOrderInfoManager.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, ConsumerOrderInfoManager.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 { + 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(); + } + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoManager.java b/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoManager.java new file mode 100644 index 00000000000..2e2850dbbc4 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoManager.java @@ -0,0 +1,644 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.alibaba.fastjson.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.ConcurrentHashMap; +import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.broker.BrokerPathConfigHelper; +import org.apache.rocketmq.common.ConfigManager; +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; + +public class ConsumerOrderInfoManager extends ConfigManager { + + 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 ConsumerOrderInfoLockManager consumerOrderInfoLockManager; + private transient BrokerController brokerController; + + public ConsumerOrderInfoManager() { + } + + public ConsumerOrderInfoManager(BrokerController brokerController) { + this.brokerController = brokerController; + this.consumerOrderInfoLockManager = new ConsumerOrderInfoLockManager(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); + } + + private void updateLockFreeTimestamp(String topic, String group, int queueId, OrderInfo orderInfo) { + if (consumerOrderInfoLockManager != null) { + consumerOrderInfoLockManager.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, isRetry, 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, isRetry, queueId, minConsumedTimes); + updateLockFreeTimestamp(topic, group, queueId, orderInfo); + } + + 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); + } + + public void clearBlock(String topic, String group, int queueId) { + table.computeIfPresent(buildKey(topic, group), (key, val) -> { + val.remove(queueId); + return val; + }); + } + + /** + * 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 + */ + 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 + */ + 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); + } + + 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().getSubscriptionGroupTable().get(group) == null) { + 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) { + ConsumerOrderInfoManager obj = RemotingSerializable.fromJson(jsonString, ConsumerOrderInfoManager.class); + if (obj != null) { + this.table = obj.table; + if (this.consumerOrderInfoLockManager != null) { + this.consumerOrderInfoLockManager.recover(this.table); + } + } + } + } + + @Override + public String encode(boolean prettyFormat) { + this.autoClean(); + return RemotingSerializable.toJson(this, prettyFormat); + } + + public void shutdown() { + if (this.consumerOrderInfoLockManager != null) { + this.consumerOrderInfoLockManager.shutdown(); + } + } + + @VisibleForTesting + protected ConsumerOrderInfoLockManager getConsumerOrderInfoLockManager() { + return consumerOrderInfoLockManager; + } + + 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 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(); + } + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/offset/LmqConsumerOffsetManager.java b/broker/src/main/java/org/apache/rocketmq/broker/offset/LmqConsumerOffsetManager.java index 7e5d77425ab..ce70b1a820f 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/offset/LmqConsumerOffsetManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/offset/LmqConsumerOffsetManager.java @@ -28,6 +28,10 @@ public class LmqConsumerOffsetManager extends ConsumerOffsetManager { private ConcurrentHashMap lmqOffsetTable = new ConcurrentHashMap<>(512); + public LmqConsumerOffsetManager() { + + } + public LmqConsumerOffsetManager(BrokerController brokerController) { super(brokerController); } @@ -88,7 +92,7 @@ public void decode(String jsonString) { if (jsonString != null) { LmqConsumerOffsetManager obj = RemotingSerializable.fromJson(jsonString, LmqConsumerOffsetManager.class); if (obj != null) { - super.offsetTable = obj.offsetTable; + super.setOffsetTable(obj.getOffsetTable()); this.lmqOffsetTable = obj.lmqOffsetTable; } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java b/broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java index de7f3fce81c..b6273e9ed58 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java @@ -17,61 +17,146 @@ package org.apache.rocketmq.broker.out; 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.Set; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.latency.BrokerFixedThreadPoolExecutor; +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.common.DataVersion; +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.AbstractBrokerRunnable; +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.UnlockCallback; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.namesrv.RegisterBrokerResult; +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.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.body.ConsumerOffsetSerializeWrapper; -import org.apache.rocketmq.common.protocol.body.KVTable; -import org.apache.rocketmq.common.protocol.body.RegisterBrokerBody; -import org.apache.rocketmq.common.protocol.body.SubscriptionGroupWrapper; -import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper; -import org.apache.rocketmq.common.protocol.header.namesrv.QueryDataVersionRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.QueryDataVersionResponseHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.RegisterBrokerRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.RegisterBrokerResponseHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.UnRegisterBrokerRequestHeader; +import org.apache.rocketmq.common.sysflag.PullSysFlag; +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.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.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.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.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.controller.AlterSyncStateSetRequestHeader; +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.UnRegisterBrokerRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; +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.TopicRouteData; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final RemotingClient remotingClient; - private final TopAddressing topAddressing = new TopAddressing(MixAll.getWSAddr()); + private final TopAddressing topAddressing = new DefaultTopAddressing(MixAll.getWSAddr()); + private final BrokerFixedThreadPoolExecutor brokerOuterExecutor = new BrokerFixedThreadPoolExecutor(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; - private BrokerFixedThreadPoolExecutor brokerOuterExecutor = new BrokerFixedThreadPoolExecutor(4, 10, 1, TimeUnit.MINUTES, - new ArrayBlockingQueue(32), new ThreadFactoryImpl("brokerOutApi_thread_", true)); + public BrokerOuterAPI(final NettyClientConfig nettyClientConfig) { - this(nettyClientConfig, null); + this(nettyClientConfig, new DynamicalExtFieldRPCHook(), new ClientMetadata()); } - public BrokerOuterAPI(final NettyClientConfig nettyClientConfig, RPCHook rpcHook) { + private BrokerOuterAPI(final NettyClientConfig nettyClientConfig, RPCHook rpcHook, ClientMetadata clientMetadata) { this.remotingClient = new NettyRemotingClient(nettyClientConfig); + this.clientMetadata = clientMetadata; this.remotingClient.registerRPCHook(rpcHook); + this.rpcClient = new RpcClientImpl(this.clientMetadata, this.remotingClient); } public void start() { @@ -83,33 +168,296 @@ public void shutdown() { this.brokerOuterExecutor.shutdown(); } + public List getNameServerAddressList() { + return this.remotingClient.getNameServerAddressList(); + } + public String fetchNameServerAddr() { try { String addrs = this.topAddressing.fetchNSAddr(); - if (addrs != null) { + if (!UtilAll.isBlank(addrs)) { if (!addrs.equals(this.nameSrvAddr)) { - log.info("name server address changed, old: {} new: {}", this.nameSrvAddr, addrs); + LOGGER.info("name server address changed, old: {} new: {}", this.nameSrvAddr, addrs); this.updateNameServerAddressList(addrs); this.nameSrvAddr = addrs; return nameSrvAddr; } } } catch (Exception e) { - log.error("fetchNameServerAddr 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) { - List lst = new ArrayList(); String[] addrArray = addrs.split(";"); - for (String addr : addrArray) { - lst.add(addr); - } + 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.size() > 0) { + final QueryDataVersionRequestHeader requestHeader = new QueryDataVersionRequestHeader(); + requestHeader.setBrokerAddr(brokerAddr); + requestHeader.setBrokerName(brokerName); + requestHeader.setBrokerId(brokerId); + requestHeader.setClusterName(clusterName); + + for (final String namesrvAddr : nameServerAddressList) { + brokerOuterExecutor.execute(new AbstractBrokerRunnable(new BrokerIdentity(clusterName, brokerName, brokerId, isInBrokerContainer)) { + + @Override + public void run0() { + 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 AbstractBrokerRunnable(new BrokerIdentity(clusterName, brokerName, brokerId, isInBrokerContainer)) { + @Override + public void run0() { + 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 = (ExchangeHAInfoResponseHeader) 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, @@ -120,10 +468,13 @@ public List registerBrokerAll( final List filterServerList, final boolean oneway, final int timeoutMills, - final boolean compressed) { + final boolean enableActingMaster, + final boolean compressed, + final Long heartbeatTimeoutMillis, + final BrokerIdentity brokerIdentity) { final List registerBrokerResultList = new CopyOnWriteArrayList<>(); - List nameServerAddressList = this.remotingClient.getNameServerAddressList(); + List nameServerAddressList = this.remotingClient.getAvailableNameSrvList(); if (nameServerAddressList != null && nameServerAddressList.size() > 0) { final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader(); @@ -132,28 +483,32 @@ public List registerBrokerAll( requestHeader.setBrokerName(brokerName); requestHeader.setClusterName(clusterName); requestHeader.setHaServerAddr(haServerAddr); - requestHeader.setCompressed(compressed); + requestHeader.setEnableActingMaster(enableActingMaster); + requestHeader.setCompressed(false); + if (heartbeatTimeoutMillis != null) { + requestHeader.setHeartbeatTimeoutMillis(heartbeatTimeoutMillis); + } RegisterBrokerBody requestBody = new RegisterBrokerBody(); - requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper); + 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() { + brokerOuterExecutor.execute(new AbstractBrokerRunnable(brokerIdentity) { @Override - public void run() { + public void run0() { try { RegisterBrokerResult result = registerBroker(namesrvAddr, oneway, timeoutMills, requestHeader, body); if (result != null) { registerBrokerResultList.add(result); } - log.info("register broker[{}]to name server {} OK", brokerId, namesrvAddr); + LOGGER.info("Registering current broker to name server completed. TargetHost={}", namesrvAddr); } catch (Exception e) { - log.warn("registerBroker Exception, {}", namesrvAddr, e); + LOGGER.error("Failed to register current broker to name server. TargetHost={}", namesrvAddr, e); } finally { countDownLatch.countDown(); } @@ -162,8 +517,10 @@ public void run() { } try { - countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { + 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) { } } @@ -222,9 +579,9 @@ public void unregisterBrokerAll( for (String namesrvAddr : nameServerAddressList) { try { this.unregisterBroker(namesrvAddr, clusterName, brokerAddr, brokerName, brokerId); - log.info("unregisterBroker OK, NamesrvAddr: {}", namesrvAddr); + LOGGER.info("unregisterBroker OK, NamesrvAddr: {}", namesrvAddr); } catch (Exception e) { - log.warn("unregisterBroker Exception, {}", namesrvAddr, e); + LOGGER.warn("unregisterBroker Exception, NamesrvAddr: {}", namesrvAddr, e); } } } @@ -263,15 +620,16 @@ public List needRegister( final String brokerName, final long brokerId, final TopicConfigSerializeWrapper topicConfigWrapper, - final int timeoutMills) { + 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() { + brokerOuterExecutor.execute(new AbstractBrokerRunnable(new BrokerIdentity(clusterName, brokerName, brokerId, isInBrokerContainer)) { @Override - public void run() { + public void run0() { try { QueryDataVersionRequestHeader requestHeader = new QueryDataVersionRequestHeader(); requestHeader.setBrokerAddr(brokerAddr); @@ -302,10 +660,10 @@ public void run() { default: break; } - log.warn("Query data version from name server {} OK,changed {}, broker {},name server {}", namesrvAddr, changed, topicConfigWrapper.getDataVersion(), nameServerDataVersion == null ? "" : nameServerDataVersion); + 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); - log.error("Query data version from name server {} Exception, {}", namesrvAddr, e); + LOGGER.error("Query data version from name server {} Exception, {}", namesrvAddr, e); } finally { countDownLatch.countDown(); } @@ -316,13 +674,13 @@ public void run() { try { countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { - log.error("query dataversion from nameserver countDownLatch await Exception", e); + LOGGER.error("query dataversion from nameserver countDownLatch await Exception", e); } } return changedList; } - public TopicConfigSerializeWrapper getAllTopicConfig( + public TopicConfigAndMappingSerializeWrapper getAllTopicConfig( final String addr) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_TOPIC_CONFIG, null); @@ -331,7 +689,43 @@ public TopicConfigSerializeWrapper getAllTopicConfig( assert response != null; switch (response.getCode()) { case ResponseCode.SUCCESS: { - return TopicConfigSerializeWrapper.decode(response.getBody(), TopicConfigSerializeWrapper.class); + return TopicConfigSerializeWrapper.decode(response.getBody(), TopicConfigAndMappingSerializeWrapper.class); + } + default: + break; + } + + throw new MQBrokerException(response.getCode(), response.getRemark(), addr); + } + + 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; @@ -394,4 +788,590 @@ public SubscriptionGroupWrapper getAllSubscriptionGroupConfig( 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 = (GetMaxOffsetResponseHeader) 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 = (GetMinOffsetResponseHeader) 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, null); + + request.setBody(requestBody.encode()); + this.remotingClient.invokeAsync(addr, request, timeoutMillis, responseFuture -> { + if (callback == null) { + return; + } + + try { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + 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())); + } + } + } catch (Throwable ignored) { + + } + }); + } + + 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, null); + + request.setBody(requestBody.encode()); + + this.remotingClient.invokeAsync(addr, request, timeoutMillis, responseFuture -> { + if (callback == null) { + return; + } + + try { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + if (response.getCode() == ResponseCode.SUCCESS) { + callback.onSuccess(); + } else { + callback.onException(new MQBrokerException(response.getCode(), response.getRemark())); + } + } + } catch (Throwable ignored) { + + } + }); + } + + 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, responseFuture -> { + RemotingCommand response = responseFuture.getResponseCommand(); + if (null != response) { + SendResult sendResult = null; + try { + sendResult = this.processSendResponse(brokerName, msg, response); + cf.complete(sendResult); + } catch (MQBrokerException | RemotingCommandException e) { + LOGGER.error("processSendResponse in sendMessageToSpecificBrokerAsync failed, msgId=" + msgId, e); + cf.completeExceptionally(e); + } + } else { + cf.complete(null); + } + + }); + } 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 = + (SendMessageResponseHeader) 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 BrokerFixedThreadPoolExecutor 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 = (ElectMasterResponseHeader) 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 (GetNextBrokerIdResponseHeader) 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 (ApplyBrokerIdResponseHeader) 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 = (RegisterBrokerToControllerResponseHeader) 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 = (GetReplicaInfoResponseHeader) 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 AbstractBrokerRunnable(new BrokerIdentity(clusterName, brokerName, brokerId, isInBrokerContainer)) { + @Override + public void run0() { + 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); + } + } + }); + } + + 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.setBname(brokerName); + + RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader); + CompletableFuture pullResultFuture = new CompletableFuture<>(); + this.remotingClient.invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> { + if (responseFuture.getCause() != null) { + pullResultFuture.complete(new PullResult(PullStatus.NO_MATCHED_MSG, -1, -1, -1, new ArrayList<>())); + return; + } + try { + PullResultExt pullResultExt = this.processPullResponse(responseFuture.getResponseCommand(), brokerAddr); + this.processPullResult(pullResultExt, brokerName, queueId); + pullResultFuture.complete(pullResultExt); + } catch (Exception e) { + pullResultFuture.complete(new PullResult(PullStatus.NO_MATCHED_MSG, -1, -1, -1, new ArrayList<>())); + } + }); + 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 = + (PullMessageResponseHeader) 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; + } + } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/pagecache/OneMessageTransfer.java b/broker/src/main/java/org/apache/rocketmq/broker/pagecache/OneMessageTransfer.java index 558f091763e..952cf4fbadd 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/pagecache/OneMessageTransfer.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/pagecache/OneMessageTransfer.java @@ -70,6 +70,7 @@ public long transferTo(WritableByteChannel target, long position) throws IOExcep return 0; } + @Override public FileRegion retain() { super.retain(); diff --git a/broker/src/main/java/org/apache/rocketmq/broker/plugin/AbstractPluginMessageStore.java b/broker/src/main/java/org/apache/rocketmq/broker/plugin/AbstractPluginMessageStore.java deleted file mode 100644 index 62fd1c5d5e4..00000000000 --- a/broker/src/main/java/org/apache/rocketmq/broker/plugin/AbstractPluginMessageStore.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.HashMap; -import java.util.LinkedList; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import org.apache.rocketmq.common.message.MessageExt; -import org.apache.rocketmq.common.message.MessageExtBatch; -import org.apache.rocketmq.store.CommitLogDispatcher; -import org.apache.rocketmq.store.ConsumeQueue; -import org.apache.rocketmq.store.GetMessageResult; -import org.apache.rocketmq.store.MessageExtBrokerInner; -import org.apache.rocketmq.store.MessageFilter; -import org.apache.rocketmq.store.MessageStore; -import org.apache.rocketmq.store.PutMessageResult; -import org.apache.rocketmq.store.QueryMessageResult; -import org.apache.rocketmq.store.SelectMappedBufferResult; -import org.apache.rocketmq.store.stats.BrokerStatsManager; - -public abstract class AbstractPluginMessageStore implements MessageStore { - protected MessageStore next = null; - 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 long getMaxOffsetInQueue(String topic, int queueId) { - return next.getMaxOffsetInQueue(topic, queueId); - } - - @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 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 long getMessageStoreTimeStamp(String topic, int queueId, long consumeQueueOffset) { - return next.getMessageStoreTimeStamp(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 void updateHaMasterAddress(String newAddr) { - next.updateHaMasterAddress(newAddr); - } - - @Override - public long slaveFallBehindMuch() { - return next.slaveFallBehindMuch(); - } - - @Override - public long now() { - return next.now(); - } - - @Override - public int cleanUnusedTopic(Set topics) { - return next.cleanUnusedTopic(topics); - } - - @Override - public void cleanExpiredConsumerQueue() { - next.cleanExpiredConsumerQueue(); - } - - @Override - public boolean checkInDiskByConsumeOffset(String topic, int queueId, long consumeOffset) { - return next.checkInDiskByConsumeOffset(topic, queueId, consumeOffset); - } - - @Override - public long dispatchBehindBytes() { - return next.dispatchBehindBytes(); - } - - @Override - public long flush() { - return next.flush(); - } - - @Override - public boolean resetWriteOffset(long phyOffset) { - return next.resetWriteOffset(phyOffset); - } - - @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 ConsumeQueue getConsumeQueue(String topic, int queueId) { - return next.getConsumeQueue(topic, queueId); - } - - @Override - public BrokerStatsManager getBrokerStatsManager() { - return next.getBrokerStatsManager(); - } - - @Override - public void cleanUnusedLmqTopic(String topic) { - next.cleanUnusedLmqTopic(topic); - } -} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/plugin/BrokerAttachedPlugin.java b/broker/src/main/java/org/apache/rocketmq/broker/plugin/BrokerAttachedPlugin.java new file mode 100644 index 00000000000..0cd2a274765 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/plugin/BrokerAttachedPlugin.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); + +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/plugin/PullMessageResultHandler.java b/broker/src/main/java/org/apache/rocketmq/broker/plugin/PullMessageResultHandler.java new file mode 100644 index 00000000000..0b9f4295c26 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/plugin/PullMessageResultHandler.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java index 3303d70e4ad..b348ecb8f09 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java @@ -17,20 +17,25 @@ package org.apache.rocketmq.broker.processor; import io.netty.channel.ChannelHandlerContext; -import java.net.InetSocketAddress; +import io.opentelemetry.api.common.Attributes; import java.net.SocketAddress; -import java.util.HashMap; import java.util.List; import java.util.Map; +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.topic.TopicValidator; +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; @@ -38,54 +43,331 @@ 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.protocol.NamespaceUtil; -import org.apache.rocketmq.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeaderV2; -import org.apache.rocketmq.common.protocol.header.SendMessageResponseHeader; +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.utils.ChannelUtil; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.AsyncNettyRequestProcessor; +import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract; 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.store.MessageExtBrokerInner; +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.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); -public abstract class AbstractSendMessageProcessor extends AsyncNettyRequestProcessor implements NettyRequestProcessor { - protected static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + protected List consumeMessageHookList; protected final static int DLQ_NUMS_PER_GROUP = 1; protected final BrokerController brokerController; - protected final SocketAddress storeHost; + protected final Random random = new Random(System.currentTimeMillis()); private List sendMessageHookList; public AbstractSendMessageProcessor(final BrokerController brokerController) { this.brokerController = brokerController; - this.storeHost = - new InetSocketAddress(brokerController.getBrokerConfig().getBrokerIP1(), brokerController - .getNettyServerConfig().getListenPort()); } - protected SendMessageContext buildMsgContext(ChannelHandlerContext ctx, - SendMessageRequestHeader requestHeader) { - if (!this.hasSendMessageHook()) { - return null; + 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 = BrokerMetricsManager.newAttributesBuilder() + .put(LABEL_CONSUMER_GROUP, requestHeader.getGroup()) + .put(LABEL_TOPIC, requestHeader.getOriginTopic()) + .put(LABEL_IS_SYSTEM, BrokerMetricsManager.isSystem(requestHeader.getOriginTopic(), requestHeader.getGroup())) + .build(); + BrokerMetricsManager.sendToDlqMessages.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 mqtraceContext = new SendMessageContext(); - mqtraceContext.setProducerGroup(requestHeader.getProducerGroup()); - mqtraceContext.setNamespace(namespace); - mqtraceContext.setTopic(requestHeader.getTopic()); - mqtraceContext.setMsgProps(requestHeader.getProperties()); - mqtraceContext.setBornHost(RemotingHelper.parseChannelRemoteAddr(ctx.channel())); - mqtraceContext.setBrokerAddr(this.brokerController.getBrokerAddr()); - mqtraceContext.setBrokerRegionId(this.brokerController.getBrokerConfig().getRegionId()); - mqtraceContext.setBornTimeStamp(requestHeader.getBornTimestamp()); + + 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()); String uniqueKey = properties.get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); @@ -96,8 +378,14 @@ protected SendMessageContext buildMsgContext(ChannelHandlerContext ctx, if (uniqueKey == null) { uniqueKey = ""; } - mqtraceContext.setMsgUniqueKey(uniqueKey); - return mqtraceContext; + sendMessageContext.setMsgUniqueKey(uniqueKey); + + if (properties.containsKey(MessageConst.PROPERTY_SHARDING_KEY)) { + sendMessageContext.setMsgType(MessageType.Order_Msg); + } else { + sendMessageContext.setMsgType(MessageType.Normal_Msg); + } + return sendMessageContext; } public boolean hasSendMessageHook() { @@ -108,7 +396,7 @@ protected MessageExtBrokerInner buildInnerMsg(final ChannelHandlerContext ctx, final SendMessageRequestHeader requestHeader, final byte[] body, TopicConfig topicConfig) { int queueIdInt = requestHeader.getQueueId(); if (queueIdInt < 0) { - queueIdInt = ThreadLocalRandom.current().nextInt(99999999) % topicConfig.getWriteQueueNums(); + queueIdInt = randomQueueId(topicConfig.getWriteQueueNums()); } int sysFlag = requestHeader.getSysFlag(); @@ -137,25 +425,30 @@ protected MessageExtBrokerInner buildInnerMsg(final ChannelHandlerContext ctx, } public SocketAddress getStoreHost() { - return storeHost; + return brokerController.getStoreHost(); } protected RemotingCommand msgContentCheck(final ChannelHandlerContext ctx, final SendMessageRequestHeader requestHeader, RemotingCommand request, final RemotingCommand response) { - if (requestHeader.getTopic().length() > Byte.MAX_VALUE) { - log.warn("putMessage message topic length too long {}", requestHeader.getTopic().length()); + 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) { - log.warn("putMessage message properties length too long {}", requestHeader.getProperties().length()); + 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) { - log.warn(" topic {} msg body size {} from {}", requestHeader.getTopic(), - request.getBody().length, ChannelUtil.getRemoteIp(ctx.channel())); + 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; @@ -164,7 +457,8 @@ protected RemotingCommand msgContentCheck(final ChannelHandlerContext ctx, } protected RemotingCommand msgCheck(final ChannelHandlerContext ctx, - final SendMessageRequestHeader requestHeader, final RemotingCommand response) { + 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); @@ -173,10 +467,15 @@ protected RemotingCommand msgCheck(final ChannelHandlerContext ctx, return response; } - if (!TopicValidator.validateTopic(requestHeader.getTopic(), response)) { + TopicValidator.ValidateTopicResult result = TopicValidator.validateTopic(requestHeader.getTopic()); + if (!result.isValid()) { + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark(result.getRemark()); return response; } - if (TopicValidator.isNotAllowedSendTopic(requestHeader.getTopic(), response)) { + if (TopicValidator.isNotAllowedSendTopic(requestHeader.getTopic())) { + response.setCode(ResponseCode.NO_PERMISSION); + response.setRemark("Sending message to topic[" + requestHeader.getTopic() + "] is forbidden."); return response; } @@ -192,7 +491,7 @@ protected RemotingCommand msgCheck(final ChannelHandlerContext ctx, } } - log.warn("the topic {} not exist, producer: {}", requestHeader.getTopic(), ctx.channel().remoteAddress()); + LOGGER.warn("the topic {} not exist, producer: {}", requestHeader.getTopic(), ctx.channel().remoteAddress()); topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageMethod( requestHeader.getTopic(), requestHeader.getDefaultTopic(), @@ -221,10 +520,10 @@ protected RemotingCommand msgCheck(final ChannelHandlerContext ctx, if (queueIdInt >= idValid) { String errorInfo = String.format("request queueId[%d] is illegal, %s Producer: %s", queueIdInt, - topicConfig.toString(), + topicConfig, RemotingHelper.parseChannelRemoteAddr(ctx.channel())); - log.warn(errorInfo); + LOGGER.warn(errorInfo); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(errorInfo); @@ -239,141 +538,29 @@ public void registerSendMessageHook(List sendMessageHookList) { protected void doResponse(ChannelHandlerContext ctx, RemotingCommand request, final RemotingCommand response) { - if (!request.isOnewayRPC()) { - try { - ctx.writeAndFlush(response); - } catch (Throwable e) { - log.error("SendMessageProcessor process request over, but response failed", e); - log.error(request.toString()); - log.error(response.toString()); - } - } + NettyRemotingAbstract.writeResponse(ctx.channel(), request, response); } - public void executeSendMessageHookBefore(final ChannelHandlerContext ctx, final RemotingCommand request, - SendMessageContext context) { + public void executeSendMessageHookBefore(SendMessageContext context) { if (hasSendMessageHook()) { for (SendMessageHook hook : this.sendMessageHookList) { try { - final SendMessageRequestHeader requestHeader = parseRequestHeader(request); - - if (null != requestHeader) { - String namespace = NamespaceUtil.getNamespaceFromResource(requestHeader.getTopic()); - context.setNamespace(namespace); - context.setProducerGroup(requestHeader.getProducerGroup()); - context.setTopic(requestHeader.getTopic()); - context.setBodyLength(request.getBody().length); - context.setMsgProps(requestHeader.getProperties()); - context.setBornHost(RemotingHelper.parseChannelRemoteAddr(ctx.channel())); - context.setBrokerAddr(this.brokerController.getBrokerAddr()); - context.setQueueId(requestHeader.getQueueId()); - } - hook.sendMessageBefore(context); - if (requestHeader != null) { - requestHeader.setProperties(context.getMsgProps()); - } + } catch (AbortProcessException e) { + throw e; } catch (Throwable e) { - // Ignore + //ignore } } } } - protected 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 = decodeSendMessageHeaderV2(request); - case RequestCode.SEND_MESSAGE: - if (null == requestHeaderV2) { - requestHeader = - (SendMessageRequestHeader) request - .decodeCommandCustomHeader(SendMessageRequestHeader.class); - } else { - requestHeader = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV1(requestHeaderV2); - } - default: - break; - } - return requestHeader; - } - - static SendMessageRequestHeaderV2 decodeSendMessageHeaderV2(RemotingCommand request) - throws RemotingCommandException { - SendMessageRequestHeaderV2 r = new SendMessageRequestHeaderV2(); - HashMap fields = request.getExtFields(); - if (fields == null) { - throw new RemotingCommandException("the ext fields is null"); - } - - String s = fields.get("a"); - checkNotNull(s, "the custom field is null"); - r.setA(s); - - s = fields.get("b"); - checkNotNull(s, "the custom field is null"); - r.setB(s); - - s = fields.get("c"); - checkNotNull(s, "the custom field is null"); - r.setC(s); - - s = fields.get("d"); - checkNotNull(s, "the custom field is null"); - r.setD(Integer.parseInt(s)); - - s = fields.get("e"); - checkNotNull(s, "the custom field is null"); - r.setE(Integer.parseInt(s)); - - s = fields.get("f"); - checkNotNull(s, "the custom field is null"); - r.setF(Integer.parseInt(s)); - - s = fields.get("g"); - checkNotNull(s, "the custom field is null"); - r.setG(Long.parseLong(s)); - - s = fields.get("h"); - checkNotNull(s, "the custom field is null"); - r.setH(Integer.parseInt(s)); - - s = fields.get("i"); - if (s != null) { - r.setI(s); - } - - s = fields.get("j"); - if (s != null) { - r.setJ(Integer.parseInt(s)); - } - - s = fields.get("k"); - if (s != null) { - r.setK(Boolean.parseBoolean(s)); - } - - s = fields.get("l"); - if (s != null) { - r.setL(Integer.parseInt(s)); - } - - s = fields.get("m"); - if (s != null) { - r.setM(Boolean.parseBoolean(s)); - } - return r; + protected SendMessageRequestHeader parseRequestHeader(RemotingCommand request) throws RemotingCommandException { + return SendMessageRequestHeader.parseRequestHeader(request); } - private static void checkNotNull(String s, String msg) throws RemotingCommandException { - if (s == null) { - throw new RemotingCommandException(msg); - } + protected int randomQueueId(int writeQueueNums) { + return ThreadLocalRandom.current().nextInt(99999999) % writeQueueNums; } public void executeSendMessageHookAfter(final RemotingCommand response, final SendMessageContext context) { @@ -391,7 +578,7 @@ public void executeSendMessageHookAfter(final RemotingCommand response, final Se } hook.sendMessageAfter(context); } catch (Throwable e) { - // Ignore + //ignore } } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java new file mode 100644 index 00000000000..2140aa881cd --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java @@ -0,0 +1,315 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.fastjson.JSON; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.broker.metrics.PopMetricsManager; +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.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.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.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 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; + } + + long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); + long maxOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); + 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; + } + + 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()) { + 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) { + 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) { + // order + 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, consumeGroup, qId); + } + } 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, ackCount); + return; + } + + ackMsg = new AckMsg(); + ackCount = 1; + } else { + // batch ack + consumeGroup = batchAck.getConsumerGroup(); + topic = ExtraInfoUtil.getRealTopic(batchAck.getTopic(), batchAck.getConsumerGroup(), ExtraInfoUtil.RETRY_TOPIC.equals(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 = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, qId); + if (minOffset == -1 || maxOffset == -1) { + POP_LOGGER.error("Illegal topic or queue found when batch ack {}", batchAck); + return; + } + + BatchAckMsg batchAckMsg = new BatchAckMsg(); + for (int i = 0; batchAck.getBitSet() != null && i < batchAck.getBitSet().length(); i++) { + if (!batchAck.getBitSet().get(i)) { + continue; + } + long offset = startOffset + i; + if (offset < minOffset || offset > maxOffset) { + continue; + } + batchAckMsg.getAckOffsetList().add(offset); + } + if (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(DataConverter.charset)); + 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())); + PutMessageResult putMessageResult = this.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("put ack msg error:" + putMessageResult); + } + System.out.printf("put ack to store %s", ackMsg); + PopMetricsManager.incPopReviveAckPutCount(ackMsg, putMessageResult.getPutMessageStatus()); + brokerController.getPopInflightMessageCounter().decrementInFlightMessageNum(topic, consumeGroup, popTime, qId, ackCount); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java index 9d188ab52c9..892a7133083 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java @@ -17,131 +17,183 @@ package org.apache.rocketmq.broker.processor; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Sets; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; +import java.io.UnsupportedEncodingException; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +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.Properties; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.acl.AccessValidator; import org.apache.rocketmq.acl.plain.PlainAccessValidator; 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.controller.ReplicasManager; import org.apache.rocketmq.broker.filter.ConsumerFilterData; import org.apache.rocketmq.broker.filter.ExpressionMessageFilter; -import org.apache.rocketmq.common.topic.TopicValidator; +import org.apache.rocketmq.broker.plugin.BrokerAttachedPlugin; +import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; import org.apache.rocketmq.broker.transaction.queue.TransactionalMessageUtil; import org.apache.rocketmq.common.AclConfig; +import org.apache.rocketmq.common.BrokerConfig; +import org.apache.rocketmq.common.LockCallback; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.PlainAccessConfig; import org.apache.rocketmq.common.TopicConfig; +import org.apache.rocketmq.common.UnlockCallback; import org.apache.rocketmq.common.UtilAll; -import org.apache.rocketmq.common.admin.ConsumeStats; -import org.apache.rocketmq.common.admin.OffsetWrapper; -import org.apache.rocketmq.common.admin.TopicOffset; -import org.apache.rocketmq.common.admin.TopicStatsTable; +import org.apache.rocketmq.common.attribute.AttributeParser; +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.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.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.body.BrokerStatsData; -import org.apache.rocketmq.common.protocol.body.BrokerStatsItem; -import org.apache.rocketmq.common.protocol.body.Connection; -import org.apache.rocketmq.common.protocol.body.ConsumeQueueData; -import org.apache.rocketmq.common.protocol.body.ConsumeStatsList; -import org.apache.rocketmq.common.protocol.body.ConsumerConnection; -import org.apache.rocketmq.common.protocol.body.GroupList; -import org.apache.rocketmq.common.protocol.body.KVTable; -import org.apache.rocketmq.common.protocol.body.LockBatchRequestBody; -import org.apache.rocketmq.common.protocol.body.LockBatchResponseBody; -import org.apache.rocketmq.common.protocol.body.ProducerConnection; -import org.apache.rocketmq.common.protocol.body.QueryConsumeQueueResponseBody; -import org.apache.rocketmq.common.protocol.body.QueryConsumeTimeSpanBody; -import org.apache.rocketmq.common.protocol.body.QueryCorrectionOffsetBody; -import org.apache.rocketmq.common.protocol.body.QueueTimeSpan; -import org.apache.rocketmq.common.protocol.body.TopicList; -import org.apache.rocketmq.common.protocol.body.UnlockBatchRequestBody; -import org.apache.rocketmq.common.protocol.header.CloneGroupOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.ConsumeMessageDirectlyResultRequestHeader; -import org.apache.rocketmq.common.protocol.header.CreateAccessConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.CreateTopicRequestHeader; -import org.apache.rocketmq.common.protocol.header.DeleteAccessConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.DeleteSubscriptionGroupRequestHeader; -import org.apache.rocketmq.common.protocol.header.DeleteTopicRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetAllTopicConfigResponseHeader; -import org.apache.rocketmq.common.protocol.header.GetBrokerAclConfigResponseHeader; -import org.apache.rocketmq.common.protocol.header.GetBrokerClusterAclConfigResponseBody; -import org.apache.rocketmq.common.protocol.header.GetBrokerClusterAclConfigResponseHeader; -import org.apache.rocketmq.common.protocol.header.GetBrokerConfigResponseHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumeStatsInBrokerHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumeStatsRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumerConnectionListRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumerRunningInfoRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumerStatusRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetEarliestMsgStoretimeRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetEarliestMsgStoretimeResponseHeader; -import org.apache.rocketmq.common.protocol.header.GetMaxOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetMaxOffsetResponseHeader; -import org.apache.rocketmq.common.protocol.header.GetMinOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetMinOffsetResponseHeader; -import org.apache.rocketmq.common.protocol.header.GetProducerConnectionListRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetTopicStatsInfoRequestHeader; -import org.apache.rocketmq.common.protocol.header.QueryConsumeQueueRequestHeader; -import org.apache.rocketmq.common.protocol.header.QueryConsumeTimeSpanRequestHeader; -import org.apache.rocketmq.common.protocol.header.QueryCorrectionOffsetHeader; -import org.apache.rocketmq.common.protocol.header.QueryTopicConsumeByWhoRequestHeader; -import org.apache.rocketmq.common.protocol.header.ResetOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.ResumeCheckHalfMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.SearchOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.SearchOffsetResponseHeader; -import org.apache.rocketmq.common.protocol.header.UpdateGlobalWhiteAddrsConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.ViewBrokerStatsDataRequestHeader; -import org.apache.rocketmq.common.protocol.header.filtersrv.RegisterFilterServerRequestHeader; -import org.apache.rocketmq.common.protocol.header.filtersrv.RegisterFilterServerResponseHeader; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.common.stats.StatsItem; import org.apache.rocketmq.common.stats.StatsSnapshot; -import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig; +import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.filter.util.BitsArray; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.AsyncNettyRequestProcessor; +import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract; 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.store.ConsumeQueue; +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.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.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.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.header.CloneGroupOffsetRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.CreateAccessConfigRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.DeleteAccessConfigRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.DeleteSubscriptionGroupRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.DeleteTopicRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.ExchangeHAInfoRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.ExchangeHAInfoResponseHeader; +import org.apache.rocketmq.remoting.protocol.header.GetAllProducerInfoRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.GetAllTopicConfigResponseHeader; +import org.apache.rocketmq.remoting.protocol.header.GetBrokerAclConfigResponseHeader; +import org.apache.rocketmq.remoting.protocol.header.GetBrokerClusterAclConfigResponseBody; +import org.apache.rocketmq.remoting.protocol.header.GetBrokerClusterAclConfigResponseHeader; +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.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.UpdateGlobalWhiteAddrsConfigRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.UpdateGroupForbiddenRequestHeader; +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.MessageExtBrokerInner; 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.config.BrokerRole; +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.timer.TimerCheckpoint; +import org.apache.rocketmq.store.timer.TimerMessageStore; +import org.apache.rocketmq.store.util.LibC; -import java.io.UnsupportedEncodingException; -import java.net.UnknownHostException; -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.Properties; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; +import static org.apache.rocketmq.remoting.protocol.RemotingCommand.buildErrorResponse; -public class AdminBrokerProcessor extends AsyncNettyRequestProcessor implements NettyRequestProcessor { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); - private final BrokerController brokerController; +public class AdminBrokerProcessor implements NettyRequestProcessor { + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + protected final BrokerController brokerController; public AdminBrokerProcessor(final BrokerController brokerController) { this.brokerController = brokerController; @@ -157,10 +209,22 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, 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: @@ -187,26 +251,34 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, 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.REGISTER_FILTER_SERVER: - return this.registerFilterServer(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: @@ -223,6 +295,10 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, return fetchAllConsumeStatsInBroker(ctx, request); case RequestCode.QUERY_CONSUME_QUEUE: return queryConsumeQueue(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.UPDATE_AND_CREATE_ACL_CONFIG: return updateAndCreateAccessConfig(ctx, request); case RequestCode.DELETE_ACL_CONFIG: @@ -235,11 +311,91 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, return resumeCheckHalfMessage(ctx, request); case RequestCode.GET_BROKER_CLUSTER_ACL_CONFIG: return getBrokerClusterAclConfig(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); 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().getSubscriptionGroupTable().get(requestHeader.getGroup()); + if (groupConfig == null) { + LOGGER.error("No group in this broker, client: {} group: {}", ctx.channel().remoteAddress(), requestHeader.getGroup()); + response.setCode(ResponseCode.SYSTEM_ERROR); + 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; + } + @Override public boolean rejectRequest() { return false; @@ -248,18 +404,81 @@ public boolean rejectRequest() { private synchronized RemotingCommand updateAndCreateTopic(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); + if (validateSlave(response)) { + return response; + } final CreateTopicRequestHeader requestHeader = (CreateTopicRequestHeader) request.decodeCommandCustomHeader(CreateTopicRequestHeader.class); - log.info("updateAndCreateTopic called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); + + LOGGER.info("Broker receive request to update or create topic={}, caller address={}", + requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(ctx.channel())); String topic = requestHeader.getTopic(); - if (!TopicValidator.validateTopic(topic, response)) { + TopicValidator.ValidateTopicResult result = TopicValidator.validateTopic(topic); + if (!result.isValid()) { + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark(result.getRemark()); return response; } - if (TopicValidator.isSystemTopic(topic, response)) { + if (brokerController.getBrokerConfig().isValidateSystemTopicWhenUpdateTopic()) { + if (TopicValidator.isSystemTopic(topic)) { + response.setCode(ResponseCode.SYSTEM_ERROR); + 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)); + + try { + this.brokerController.getTopicConfigManager().updateTopicConfig(topicConfig); + 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; + } + + 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.ValidateTopicResult result = TopicValidator.validateTopic(topic); + if (!result.isValid()) { + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark(result.getRemark()); return response; } + if (brokerController.getBrokerConfig().isValidateSystemTopicWhenUpdateTopic()) { + if (TopicValidator.isSystemTopic(topic)) { + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark("The topic[" + topic + "] is conflict with system topic."); + return response; + } + } + boolean force = false; + if (requestHeader.getForce() != null && requestHeader.getForce()) { + force = true; + } TopicConfig topicConfig = new TopicConfig(topic); topicConfig.setReadQueueNums(requestHeader.getReadQueueNums()); @@ -268,43 +487,53 @@ private synchronized RemotingCommand updateAndCreateTopic(ChannelHandlerContext topicConfig.setPerm(requestHeader.getPerm()); topicConfig.setTopicSysFlag(requestHeader.getTopicSysFlag() == null ? 0 : requestHeader.getTopicSysFlag()); - this.brokerController.getTopicConfigManager().updateTopicConfig(topicConfig); + try { + this.brokerController.getTopicConfigManager().updateTopicConfig(topicConfig); - this.brokerController.registerIncrementBrokerData(topicConfig, this.brokerController.getTopicConfigManager().getDataVersion()); + this.brokerController.getTopicQueueMappingManager().updateTopicQueueMapping(topicQueueMappingDetail, force, false, true); - response.setCode(ResponseCode.SUCCESS); + 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); + if (validateSlave(response)) { + return response; + } DeleteTopicRequestHeader requestHeader = (DeleteTopicRequestHeader) request.decodeCommandCustomHeader(DeleteTopicRequestHeader.class); - log.info("deleteTopic called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); + LOGGER.info("AdminBrokerProcessor#deleteTopic: broker receive request to delete topic={}, caller={}", + requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(ctx.channel())); String topic = requestHeader.getTopic(); - if (!TopicValidator.validateTopic(topic, response)) { - return response; - } - if (TopicValidator.isSystemTopic(topic, response)) { + TopicValidator.ValidateTopicResult result = TopicValidator.validateTopic(topic); + if (!result.isValid()) { + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark(result.getRemark()); return response; } - - if (MixAll.isLmq(topic)) { - this.brokerController.getMessageStore().cleanUnusedLmqTopic(topic); - response.setCode(ResponseCode.SUCCESS); - response.setRemark(null); - return response; + if (brokerController.getBrokerConfig().isValidateSystemTopicWhenUpdateTopic()) { + if (TopicValidator.isSystemTopic(topic)) { + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark("The topic[" + topic + "] is conflict with system topic."); + return response; + } } - this.brokerController.getTopicConfigManager().deleteTopicConfig(topic); - this.brokerController.getMessageStore() - .cleanUnusedTopic(this.brokerController.getTopicConfigManager().getTopicConfigTable().keySet()); - if (this.brokerController.getBrokerConfig().isAutoDeleteUnusedStats()) { - this.brokerController.getBrokerStatsManager().onTopicDeleted(requestHeader.getTopic()); - } + this.brokerController.getTopicConfigManager().deleteTopicConfig(requestHeader.getTopic()); + this.brokerController.getTopicQueueMappingManager().delete(requestHeader.getTopic()); + this.brokerController.getConsumerOffsetManager().cleanOffsetByTopic(requestHeader.getTopic()); + this.brokerController.getPopInflightMessageCounter().clearInFlightMessageNumByTopicName(requestHeader.getTopic()); + this.brokerController.getMessageStore().deleteTopics(Sets.newHashSet(requestHeader.getTopic())); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; @@ -334,16 +563,16 @@ private synchronized RemotingCommand updateAndCreateAccessConfig(ChannelHandlerC response.setOpaque(request.getOpaque()); response.markResponseType(); response.setRemark(null); - ctx.writeAndFlush(response); + NettyRemotingAbstract.writeResponse(ctx.channel(), request, response); } else { - String errorMsg = "The accesskey[" + requestHeader.getAccessKey() + "] corresponding to accessConfig has been updated failed."; - log.warn(errorMsg); + String errorMsg = "The accessKey[" + requestHeader.getAccessKey() + "] corresponding to accessConfig has been updated failed."; + LOGGER.warn(errorMsg); response.setCode(ResponseCode.UPDATE_AND_CREATE_ACL_CONFIG_FAILED); response.setRemark(errorMsg); return response; } } catch (Exception e) { - log.error("Failed to generate a proper update accessvalidator response", e); + LOGGER.error("Failed to generate a proper update accessValidator response", e); response.setCode(ResponseCode.UPDATE_AND_CREATE_ACL_CONFIG_FAILED); response.setRemark(e.getMessage()); return response; @@ -358,7 +587,7 @@ private synchronized RemotingCommand deleteAccessConfig(ChannelHandlerContext ct final DeleteAccessConfigRequestHeader requestHeader = (DeleteAccessConfigRequestHeader) request.decodeCommandCustomHeader(DeleteAccessConfigRequestHeader.class); - log.info("DeleteAccessConfig called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); + LOGGER.info("DeleteAccessConfig called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); try { String accessKey = requestHeader.getAccessKey(); @@ -368,17 +597,17 @@ private synchronized RemotingCommand deleteAccessConfig(ChannelHandlerContext ct response.setOpaque(request.getOpaque()); response.markResponseType(); response.setRemark(null); - ctx.writeAndFlush(response); + NettyRemotingAbstract.writeResponse(ctx.channel(), request, response); } else { - String errorMsg = "The accesskey[" + requestHeader.getAccessKey() + "] corresponding to accessConfig has been deleted failed."; - log.warn(errorMsg); + String errorMsg = "The accessKey[" + requestHeader.getAccessKey() + "] corresponding to accessConfig has been deleted failed."; + LOGGER.warn(errorMsg); response.setCode(ResponseCode.DELETE_ACL_CONFIG_FAILED); response.setRemark(errorMsg); return response; } } catch (Exception e) { - log.error("Failed to generate a proper delete accessvalidator response", e); + LOGGER.error("Failed to generate a proper delete accessValidator response", e); response.setCode(ResponseCode.DELETE_ACL_CONFIG_FAILED); response.setRemark(e.getMessage()); return response; @@ -397,21 +626,22 @@ private synchronized RemotingCommand updateGlobalWhiteAddrsConfig(ChannelHandler try { AccessValidator accessValidator = this.brokerController.getAccessValidatorMap().get(PlainAccessValidator.class); - if (accessValidator.updateGlobalWhiteAddrsConfig(UtilAll.split(requestHeader.getGlobalWhiteAddrs(), ","))) { + if (accessValidator.updateGlobalWhiteAddrsConfig(UtilAll.split(requestHeader.getGlobalWhiteAddrs(), ","), + requestHeader.getAclFileFullPath())) { response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); response.markResponseType(); response.setRemark(null); - ctx.writeAndFlush(response); + NettyRemotingAbstract.writeResponse(ctx.channel(), request, response); } else { String errorMsg = "The globalWhiteAddresses[" + requestHeader.getGlobalWhiteAddrs() + "] has been updated failed."; - log.warn(errorMsg); + LOGGER.warn(errorMsg); response.setCode(ResponseCode.UPDATE_GLOBAL_WHITE_ADDRS_CONFIG_FAILED); response.setRemark(errorMsg); return response; } } catch (Exception e) { - log.error("Failed to generate a proper update globalWhiteAddresses response", e); + LOGGER.error("Failed to generate a proper update globalWhiteAddresses response", e); response.setCode(ResponseCode.UPDATE_GLOBAL_WHITE_ADDRS_CONFIG_FAILED); response.setRemark(e.getMessage()); return response; @@ -424,12 +654,11 @@ private RemotingCommand getBrokerAclConfigVersion(ChannelHandlerContext ctx, Rem final RemotingCommand response = RemotingCommand.createResponseCommand(GetBrokerAclConfigResponseHeader.class); - final GetBrokerAclConfigResponseHeader responseHeader = (GetBrokerAclConfigResponseHeader)response.readCustomHeader(); + final GetBrokerAclConfigResponseHeader responseHeader = (GetBrokerAclConfigResponseHeader) response.readCustomHeader(); try { AccessValidator accessValidator = this.brokerController.getAccessValidatorMap().get(PlainAccessValidator.class); - responseHeader.setAllAclFileVersion(JSON.toJSONString(accessValidator.getAllAclConfigVersion())); responseHeader.setVersion(accessValidator.getAclConfigVersion()); responseHeader.setBrokerAddr(this.brokerController.getBrokerAddr()); responseHeader.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName()); @@ -439,7 +668,7 @@ private RemotingCommand getBrokerAclConfigVersion(ChannelHandlerContext ctx, Rem response.setRemark(null); return response; } catch (Exception e) { - log.error("Failed to generate a proper getBrokerAclConfigVersion response", e); + LOGGER.error("Failed to generate a proper getBrokerAclConfigVersion response", e); } return null; @@ -460,7 +689,7 @@ private RemotingCommand getBrokerClusterAclConfig(ChannelHandlerContext ctx, Rem response.setRemark(null); return response; } catch (Exception e) { - log.error("Failed to generate a proper getBrokerClusterAclConfig response", e); + LOGGER.error("Failed to generate a proper getBrokerClusterAclConfig response", e); } return null; @@ -469,7 +698,7 @@ private RemotingCommand getBrokerClusterAclConfig(ChannelHandlerContext ctx, Rem 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); + RemotingCommand.createResponseCommand(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED, error); return response; } @@ -478,19 +707,27 @@ private RemotingCommand getAllTopicConfig(ChannelHandlerContext ctx, RemotingCom // final GetAllTopicConfigResponseHeader responseHeader = // (GetAllTopicConfigResponseHeader) response.readCustomHeader(); - String content = this.brokerController.getTopicConfigManager().encode(); + TopicConfigAndMappingSerializeWrapper topicConfigAndMappingSerializeWrapper = new TopicConfigAndMappingSerializeWrapper(); + + topicConfigAndMappingSerializeWrapper.setDataVersion(this.brokerController.getTopicConfigManager().getDataVersion()); + topicConfigAndMappingSerializeWrapper.setTopicConfigTable(this.brokerController.getTopicConfigManager().getTopicConfigTable()); + + topicConfigAndMappingSerializeWrapper.setMappingDataVersion(this.brokerController.getTopicQueueMappingManager().getDataVersion()); + topicConfigAndMappingSerializeWrapper.setTopicQueueMappingDetailMap(this.brokerController.getTopicQueueMappingManager().getTopicQueueMappingTable()); + + String content = topicConfigAndMappingSerializeWrapper.toJson(); if (content != null && content.length() > 0) { try { response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { - log.error("", e); + LOGGER.error("", e); response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark("UnsupportedEncodingException " + e); + response.setRemark("UnsupportedEncodingException " + e.getMessage()); return response; } } else { - log.error("No topic in this broker, client: {}", ctx.channel().remoteAddress()); + 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; @@ -502,10 +739,166 @@ private RemotingCommand getAllTopicConfig(ChannelHandlerContext ctx, RemotingCom 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.entrySet().stream().forEach(i -> { + try { + String consumerGroup = String.valueOf(i.getKey()); + Long threshold = Long.valueOf(String.valueOf(i.getValue())); + this.brokerController.getColdDataCgCtrService().addOrUpdateGroupConfig(consumerGroup, threshold); + } catch (Exception e) { + LOGGER.error("updateColdDataFlowCtrGroupConfig properties on entry error, key: {}, val: {}", i.getKey(), i.getValue(), 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.SYSTEM_ERROR); + 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); - log.info("updateBrokerConfig called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); + 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) { @@ -513,20 +906,30 @@ private synchronized RemotingCommand updateBrokerConfig(ChannelHandlerContext ct String bodyStr = new String(body, MixAll.DEFAULT_CHARSET); Properties properties = MixAll.string2Properties(bodyStr); if (properties != null) { - log.info("updateBrokerConfig, new config: [{}] client: {} ", properties, ctx.channel().remoteAddress()); + LOGGER.info("updateBrokerConfig, new config: [{}] client: {} ", properties, callerAddress); + + if (properties.containsKey("brokerConfigPath")) { + response.setCode(ResponseCode.NO_PERMISSION); + response.setRemark("Can not update config path"); + return response; + } + this.brokerController.getConfiguration().update(properties); if (properties.containsKey("brokerPermission")) { - this.brokerController.getTopicConfigManager().getDataVersion().nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + this.brokerController.getTopicConfigManager().getDataVersion().nextVersion(stateMachineVersion); this.brokerController.registerBrokerAll(false, false, true); } + } else { - log.error("string2Properties error"); + LOGGER.error("string2Properties error"); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("string2Properties error"); return response; } } catch (UnsupportedEncodingException e) { - log.error("", e); + LOGGER.error("AdminBrokerProcessor#updateBrokerConfig: unexpected error, caller={}", + callerAddress, e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; @@ -548,7 +951,8 @@ private RemotingCommand getBrokerConfig(ChannelHandlerContext ctx, RemotingComma try { response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { - log.error("", e); + LOGGER.error("AdminBrokerProcessor#getBrokerConfig: unexpected error, caller={}", + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); @@ -563,6 +967,63 @@ private RemotingCommand getBrokerConfig(ChannelHandlerContext ctx, RemotingComma 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); + if (offset > 0) { + offset = item.computeStaticQueueOffsetStrictly(offset); + break; + } + } else { + requestHeader.setLo(false); + requestHeader.setTimestamp(timestamp); + requestHeader.setQueueId(item.getQueueId()); + requestHeader.setBname(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); @@ -570,6 +1031,13 @@ private RemotingCommand searchOffsetByTimestamp(ChannelHandlerContext ctx, 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; + } + long offset = this.brokerController.getMessageStore().getOffsetInQueueByTime(requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getTimestamp()); @@ -580,6 +1048,51 @@ private RemotingCommand searchOffsetByTimestamp(ChannelHandlerContext ctx, 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.setBname(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); @@ -587,6 +1100,12 @@ private RemotingCommand getMaxOffset(ChannelHandlerContext ctx, final GetMaxOffsetRequestHeader requestHeader = (GetMaxOffsetRequestHeader) request.decodeCommandCustomHeader(GetMaxOffsetRequestHeader.class); + TopicQueueMappingContext mappingContext = this.brokerController.getTopicQueueMappingManager().buildTopicQueueMappingContext(requestHeader); + RemotingCommand rewriteResult = rewriteRequestForStaticTopic(requestHeader, mappingContext); + if (rewriteResult != null) { + return rewriteResult; + } + long offset = this.brokerController.getMessageStore().getMaxOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); responseHeader.setOffset(offset); @@ -596,19 +1115,107 @@ private RemotingCommand getMaxOffset(ChannelHandlerContext ctx, 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.setBname(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 RemotingCommand response = RemotingCommand.createResponseCommand(GetMinOffsetResponseHeader.class); - final GetMinOffsetResponseHeader responseHeader = (GetMinOffsetResponseHeader) response.readCustomHeader(); 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()); + } + } - long offset = this.brokerController.getMessageStore().getMinOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); + private RemotingCommand rewriteRequestForStaticTopic(GetEarliestMsgStoretimeRequestHeader requestHeader, + TopicQueueMappingContext mappingContext) { + if (mappingContext.getMappingDetail() == null) { + return null; + } - responseHeader.setOffset(offset); - response.setCode(ResponseCode.SUCCESS); - response.setRemark(null); - return response; + 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.setBname(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, @@ -618,6 +1225,12 @@ private RemotingCommand getEarliestMsgStoretime(ChannelHandlerContext ctx, 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()); @@ -646,10 +1259,78 @@ private RemotingCommand lockBatchMQ(ChannelHandlerContext ctx, final RemotingCommand response = RemotingCommand.createResponseCommand(null); LockBatchRequestBody requestBody = LockBatchRequestBody.decode(request.getBody(), LockBatchRequestBody.class); - Set lockOKMQSet = this.brokerController.getRebalanceLockManager().tryLockBatch( + 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); @@ -665,10 +1346,36 @@ private RemotingCommand unlockBatchMQ(ChannelHandlerContext ctx, final RemotingCommand response = RemotingCommand.createResponseCommand(null); UnlockBatchRequestBody requestBody = UnlockBatchRequestBody.decode(request.getBody(), UnlockBatchRequestBody.class); - this.brokerController.getRebalanceLockManager().unlockBatch( - requestBody.getConsumerGroup(), - requestBody.getMqSet(), - requestBody.getClientId()); + 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); @@ -678,8 +1385,12 @@ private RemotingCommand unlockBatchMQ(ChannelHandlerContext ctx, private RemotingCommand updateAndCreateSubscriptionGroup(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); + if (validateSlave(response)) { + return response; + } - log.info("updateAndCreateSubscriptionGroup called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); + LOGGER.info("AdminBrokerProcessor#updateAndCreateSubscriptionGroup called by {}", + RemotingHelper.parseChannelRemoteAddr(ctx.channel())); SubscriptionGroupConfig config = RemotingSerializable.decode(request.getBody(), SubscriptionGroupConfig.class); if (config != null) { @@ -691,6 +1402,26 @@ private RemotingCommand updateAndCreateSubscriptionGroup(ChannelHandlerContext c return response; } + private void initConsumerOffset(String clientHost, String groupName, int mode, TopicConfig topicConfig) { + 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(null); @@ -699,14 +1430,14 @@ private RemotingCommand getAllSubscriptionGroup(ChannelHandlerContext ctx, try { response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { - log.error("", e); + LOGGER.error("UnsupportedEncodingException getAllSubscriptionGroup", e); response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark("UnsupportedEncodingException " + e); + response.setRemark("UnsupportedEncodingException " + e.getMessage()); return response; } } else { - log.error("No subscription group in this broker, client:{} ", ctx.channel().remoteAddress()); + 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; @@ -721,15 +1452,20 @@ private RemotingCommand getAllSubscriptionGroup(ChannelHandlerContext ctx, private RemotingCommand deleteSubscriptionGroup(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); + if (validateSlave(response)) { + return response; + } DeleteSubscriptionGroupRequestHeader requestHeader = (DeleteSubscriptionGroupRequestHeader) request.decodeCommandCustomHeader(DeleteSubscriptionGroupRequestHeader.class); - log.info("deleteSubscriptionGroup called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); + LOGGER.info("AdminBrokerProcessor#deleteSubscriptionGroup, caller={}", + RemotingHelper.parseChannelRemoteAddr(ctx.channel())); this.brokerController.getSubscriptionGroupManager().deleteSubscriptionGroupConfig(requestHeader.getGroupName()); - if (requestHeader.isRemoveOffset()) { + if (requestHeader.isCleanOffset()) { this.brokerController.getConsumerOffsetManager().removeOffset(requestHeader.getGroupName()); + this.brokerController.getPopInflightMessageCounter().clearInFlightMessageNumByGroupName(requestHeader.getGroupName()); } if (this.brokerController.getBrokerConfig().isAutoDeleteUnusedStats()) { @@ -763,12 +1499,14 @@ private RemotingCommand getTopicStatsInfo(ChannelHandlerContext ctx, TopicOffset topicOffset = new TopicOffset(); long min = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, i); - if (min < 0) + if (min < 0) { min = 0; + } long max = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, i); - if (max < 0) + if (max < 0) { max = 0; + } long timestamp = 0; if (max > 0) { @@ -829,6 +1567,25 @@ private RemotingCommand getConsumerConnectionList(ChannelHandlerContext ctx, 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); @@ -871,7 +1628,7 @@ private RemotingCommand getConsumeStats(ChannelHandlerContext ctx, ConsumeStats consumeStats = new ConsumeStats(); - Set topics = new HashSet(); + Set topics = new HashSet<>(); if (UtilAll.isBlank(requestHeader.getTopic())) { topics = this.brokerController.getConsumerOffsetManager().whichTopicByConsumer(requestHeader.getConsumerGroup()); } else { @@ -881,17 +1638,21 @@ private RemotingCommand getConsumeStats(ChannelHandlerContext ctx, for (String topic : topics) { TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { - log.warn("consumeStats, topic config not exist, {}", topic); + LOGGER.warn("AdminBrokerProcessor#getConsumeStats: topic config does not exist, topic={}", topic); continue; } + TopicQueueMappingDetail mappingDetail = this.brokerController.getTopicQueueMappingManager().getTopicQueueMapping(topic); + { SubscriptionData findSubscriptionData = this.brokerController.getConsumerManager().findSubscriptionData(requestHeader.getConsumerGroup(), topic); if (null == findSubscriptionData && this.brokerController.getConsumerManager().findSubscriptionDataCount(requestHeader.getConsumerGroup()) > 0) { - log.warn("consumeStats, the consumer group[{}], topic[{}] not exist", requestHeader.getConsumerGroup(), topic); + LOGGER.warn( + "AdminBrokerProcessor#getConsumeStats: topic does not exist in consumer group's subscription, " + + "topic={}, consumer group={}", topic, requestHeader.getConsumerGroup()); continue; } } @@ -905,18 +1666,28 @@ private RemotingCommand getConsumeStats(ChannelHandlerContext ctx, OffsetWrapper offsetWrapper = new OffsetWrapper(); long brokerOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, i); - if (brokerOffset < 0) + if (brokerOffset < 0) { brokerOffset = 0; + } long consumerOffset = this.brokerController.getConsumerOffsetManager().queryOffset( - requestHeader.getConsumerGroup(), - topic, - i); - if (consumerOffset < 0) - consumerOffset = 0; + 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) { @@ -950,14 +1721,14 @@ private RemotingCommand getAllConsumerOffset(ChannelHandlerContext ctx, Remoting try { response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { - log.error("get all consumer offset from master error.", e); + LOGGER.error("get all consumer offset from master error.", e); response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark("UnsupportedEncodingException " + e); + response.setRemark("UnsupportedEncodingException " + e.getMessage()); return response; } } else { - log.error("No consumer offset in this broker, client: {} ", ctx.channel().remoteAddress()); + 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; @@ -972,28 +1743,50 @@ private RemotingCommand getAllConsumerOffset(ChannelHandlerContext ctx, Remoting private RemotingCommand getAllDelayOffset(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); - if (!(this.brokerController.getMessageStore() instanceof DefaultMessageStore)) { - log.error("Delay offset not supported in this messagetore, client: {} ", ctx.channel().remoteAddress()); + 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("Delay offset not supported in this messagetore"); + response.setRemark("No delay offset in this broker"); return response; } - String content = ((DefaultMessageStore) this.brokerController.getMessageStore()).getScheduleMessageService().encode(); + 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.length() > 0) { try { response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { - log.error("Get all delay offset from master error.", e); + LOGGER.error("get all message request mode from master error.", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("UnsupportedEncodingException " + e); return response; } } else { - log.error("No delay offset in this broker, client: {} ", ctx.channel().remoteAddress()); + LOGGER.error("No message request mode in this broker, client: {} ", ctx.channel().remoteAddress()); response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark("No delay offset in this broker"); + response.setRemark("No message request mode in this broker"); return response; } @@ -1007,9 +1800,19 @@ public RemotingCommand resetOffset(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final ResetOffsetRequestHeader requestHeader = (ResetOffsetRequestHeader) request.decodeCommandCustomHeader(ResetOffsetRequestHeader.class); - log.info("[reset-offset] reset offset started by {}. topic={}, group={}, timestamp={}, isForce={}", + 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) { @@ -1021,12 +1824,105 @@ public RemotingCommand resetOffset(ChannelHandlerContext ctx, requestHeader.getTimestamp(), requestHeader.isForce(), isC); } + private Long searchOffsetByTimestamp(String topic, int queueId, long timestamp) { + 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().getTopicConfigTable().get(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; + } + + 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); + } + } + + 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()) { + brokerController.getPopInflightMessageCounter().clearInFlightMessageNum(topic, group, entry.getKey()); + 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); - log.info("[get-consumer-status] get consumer status by {}. topic={}, group={}", + 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(), @@ -1056,21 +1952,45 @@ private RemotingCommand queryTopicConsumeByWho(ChannelHandlerContext ctx, return response; } - private RemotingCommand registerFilterServer(ChannelHandlerContext ctx, + 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(RegisterFilterServerResponseHeader.class); - final RegisterFilterServerResponseHeader responseHeader = (RegisterFilterServerResponseHeader) response.readCustomHeader(); - final RegisterFilterServerRequestHeader requestHeader = - (RegisterFilterServerRequestHeader) request.decodeCommandCustomHeader(RegisterFilterServerRequestHeader.class); + final RemotingCommand response = RemotingCommand.createResponseCommand(null); + QuerySubscriptionByConsumerRequestHeader requestHeader = + (QuerySubscriptionByConsumerRequestHeader) request.decodeCommandCustomHeader(QuerySubscriptionByConsumerRequestHeader.class); - this.brokerController.getFilterServerManager().registerFilterServer(ctx.channel(), requestHeader.getFilterServerAddr()); + SubscriptionData subscriptionData = this.brokerController.getConsumerManager() + .findSubscriptionData(requestHeader.getGroup(), requestHeader.getTopic()); - responseHeader.setBrokerId(this.brokerController.getBrokerConfig().getBrokerId()); - responseHeader.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName()); + 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, @@ -1087,7 +2007,7 @@ private RemotingCommand queryConsumeTimeSpan(ChannelHandlerContext ctx, return response; } - List timeSpanSet = new ArrayList(); + List timeSpanSet = new ArrayList<>(); for (int i = 0; i < topicConfig.getWriteQueueNums(); i++) { QueueTimeSpan timeSpan = new QueueTimeSpan(); MessageQueue mq = new MessageQueue(); @@ -1143,20 +2063,30 @@ private RemotingCommand getSystemTopicListFromBroker(ChannelHandlerContext ctx, } public RemotingCommand cleanExpiredConsumeQueue() { - log.warn("invoke cleanExpiredConsumeQueue start."); + LOGGER.info("AdminBrokerProcessor#cleanExpiredConsumeQueue: start."); final RemotingCommand response = RemotingCommand.createResponseCommand(null); brokerController.getMessageStore().cleanExpiredConsumerQueue(); - log.warn("invoke cleanExpiredConsumeQueue end."); + 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() { - log.warn("invoke cleanUnusedTopic start."); + LOGGER.warn("invoke cleanUnusedTopic start."); final RemotingCommand response = RemotingCommand.createResponseCommand(null); brokerController.getMessageStore().cleanUnusedTopic(brokerController.getTopicConfigManager().getTopicConfigTable().keySet()); - log.warn("invoke cleanUnusedTopic end."); + LOGGER.warn("invoke cleanUnusedTopic end."); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; @@ -1181,7 +2111,7 @@ private RemotingCommand queryCorrectionOffset(ChannelHandlerContext ctx, .queryMinOffsetInAllGroup(requestHeader.getTopic(), requestHeader.getFilterGroups()); Map compareOffset = - this.brokerController.getConsumerOffsetManager().queryOffset(requestHeader.getTopic(), requestHeader.getCompareGroup()); + this.brokerController.getConsumerOffsetManager().queryOffset(requestHeader.getCompareGroup(), requestHeader.getTopic()); if (compareOffset != null && !compareOffset.isEmpty()) { for (Map.Entry entry : compareOffset.entrySet()) { @@ -1204,7 +2134,22 @@ private RemotingCommand consumeMessageDirectly(ChannelHandlerContext ctx, 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().getSubscriptionGroupTable().get(requestHeader.getConsumerGroup()); + if (groupConfig != null) { + request.addExtField("groupSysFlag", String.valueOf(groupConfig.getGroupSysFlag())); + } + } SelectMappedBufferResult selectMappedBufferResult = null; try { MessageId messageId = MessageDecoder.decodeMessageId(requestHeader.getMsgId()); @@ -1234,14 +2179,14 @@ private RemotingCommand cloneGroupOffset(ChannelHandlerContext ctx, if (UtilAll.isBlank(requestHeader.getTopic())) { topics = this.brokerController.getConsumerOffsetManager().whichTopicByConsumer(requestHeader.getSrcGroup()); } else { - topics = new HashSet(); + topics = new HashSet<>(); topics.add(requestHeader.getTopic()); } for (String topic : topics) { TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { - log.warn("[cloneGroupOffset], topic config not exist, {}", topic); + LOGGER.warn("[cloneGroupOffset], topic config not exist, {}", topic); continue; } @@ -1251,7 +2196,9 @@ private RemotingCommand cloneGroupOffset(ChannelHandlerContext ctx, this.brokerController.getConsumerManager().findSubscriptionData(requestHeader.getSrcGroup(), topic); if (this.brokerController.getConsumerManager().findSubscriptionDataCount(requestHeader.getSrcGroup()) > 0 && findSubscriptionData == null) { - log.warn("[cloneGroupOffset], the consumer group[{}], topic[{}] not exist", requestHeader.getSrcGroup(), topic); + LOGGER.warn( + "AdminBrokerProcessor#cloneGroupOffset: topic does not exist in consumer group's " + + "subscription, topic={}, consumer group={}", topic, requestHeader.getSrcGroup()); continue; } } @@ -1324,19 +2271,21 @@ private RemotingCommand fetchAllConsumeStatsInBroker(ChannelHandlerContext ctx, brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable(); List>> brokerConsumeStatsList = - new ArrayList>>(); + new ArrayList<>(); long totalDiff = 0L; - + long totalInflightDiff = 0L; for (String group : subscriptionGroups.keySet()) { - Map> subscripTopicConsumeMap = new HashMap>(); + Map> subscripTopicConsumeMap = new HashMap<>(); Set topics = this.brokerController.getConsumerOffsetManager().whichTopicByConsumer(group); - List consumeStatsList = new ArrayList(); + List consumeStatsList = new ArrayList<>(); for (String topic : topics) { ConsumeStats consumeStats = new ConsumeStats(); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { - log.warn("consumeStats, topic config not exist, {}", topic); + LOGGER.warn( + "AdminBrokerProcessor#fetchAllConsumeStatsInBroker: topic config does not exist, topic={}", + topic); continue; } @@ -1349,7 +2298,9 @@ private RemotingCommand fetchAllConsumeStatsInBroker(ChannelHandlerContext ctx, if (null == findSubscriptionData && this.brokerController.getConsumerManager().findSubscriptionDataCount(group) > 0) { - log.warn("consumeStats, the consumer group[{}], topic[{}] not exist", group, topic); + LOGGER.warn( + "AdminBrokerProcessor#fetchAllConsumeStatsInBroker: topic does not exist in consumer " + + "group's subscription, topic={}, consumer group={}", topic, group); continue; } } @@ -1361,8 +2312,9 @@ private RemotingCommand fetchAllConsumeStatsInBroker(ChannelHandlerContext ctx, mq.setQueueId(i); OffsetWrapper offsetWrapper = new OffsetWrapper(); long brokerOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, i); - if (brokerOffset < 0) + if (brokerOffset < 0) { brokerOffset = 0; + } long consumerOffset = this.brokerController.getConsumerOffsetManager().queryOffset( group, topic, @@ -1386,6 +2338,7 @@ private RemotingCommand fetchAllConsumeStatsInBroker(ChannelHandlerContext ctx, consumeTps += consumeStats.getConsumeTps(); consumeStats.setConsumeTps(consumeTps); totalDiff += consumeStats.computeTotalDiff(); + totalInflightDiff += consumeStats.computeInflightTotalDiff(); consumeStatsList.add(consumeStats); } subscripTopicConsumeMap.put(group, consumeStatsList); @@ -1395,6 +2348,7 @@ private RemotingCommand fetchAllConsumeStatsInBroker(ChannelHandlerContext ctx, consumeStats.setBrokerAddr(brokerController.getBrokerAddr()); consumeStats.setConsumeStatsList(brokerConsumeStatsList); consumeStats.setTotalDiff(totalDiff); + consumeStats.setTotalInflightDiff(totalInflightDiff); response.setBody(consumeStats.encode()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); @@ -1403,6 +2357,15 @@ private RemotingCommand fetchAllConsumeStatsInBroker(ChannelHandlerContext ctx, private HashMap prepareRuntimeInfo() { HashMap runtimeInfo = this.brokerController.getMessageStore().getRuntimeInfo(); + + for (BrokerAttachedPlugin brokerAttachedPlugin : brokerController.getBrokerAttachedPlugins()) { + if (brokerAttachedPlugin != null) { + brokerAttachedPlugin.buildRuntimeInfo(runtimeInfo); + } + } + + this.brokerController.getScheduleMessageService().buildRunningStats(runtimeInfo); + runtimeInfo.put("brokerActive", String.valueOf(this.brokerController.isSpecialServiceRunning())); runtimeInfo.put("brokerVersionDesc", MQVersion.getVersionDesc(MQVersion.CURRENT_VERSION)); runtimeInfo.put("brokerVersion", String.valueOf(MQVersion.CURRENT_VERSION)); @@ -1416,8 +2379,38 @@ private HashMap prepareRuntimeInfo() { runtimeInfo.put("msgGetTotalTodayMorning", String.valueOf(this.brokerController.getBrokerStats().getMsgGetTotalTodayMorning())); runtimeInfo.put("msgGetTotalTodayNow", String.valueOf(this.brokerController.getBrokerStats().getMsgGetTotalTodayNow())); - runtimeInfo.put("sendThreadPoolQueueSize", String.valueOf(this.brokerController.getSendThreadPoolQueue().size())); + 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())); @@ -1425,36 +2418,22 @@ private HashMap prepareRuntimeInfo() { 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("EndTransactionQueueSize", String.valueOf(this.brokerController.getEndTransactionThreadPoolQueue().size())); - runtimeInfo.put("EndTransactionThreadPoolQueueCapacity", - String.valueOf(this.brokerController.getBrokerConfig().getEndTransactionPoolQueueCapacity())); - - runtimeInfo.put("dispatchBehindBytes", String.valueOf(this.brokerController.getMessageStore().dispatchBehindBytes())); - runtimeInfo.put("pageCacheLockTimeMills", String.valueOf(this.brokerController.getMessageStore().lockTimeMills())); - runtimeInfo.put("sendThreadPoolQueueHeadWaitTimeMills", String.valueOf(this.brokerController.headSlowTimeMills4SendThreadPoolQueue())); - runtimeInfo.put("pullThreadPoolQueueHeadWaitTimeMills", String.valueOf(this.brokerController.headSlowTimeMills4PullThreadPoolQueue())); + runtimeInfo.put("pullThreadPoolQueueHeadWaitTimeMills", String.valueOf(brokerController.headSlowTimeMills4PullThreadPoolQueue())); runtimeInfo.put("queryThreadPoolQueueHeadWaitTimeMills", String.valueOf(this.brokerController.headSlowTimeMills4QueryThreadPoolQueue())); + runtimeInfo.put("litePullThreadPoolQueueHeadWaitTimeMills", String.valueOf(brokerController.headSlowTimeMills4LitePullThreadPoolQueue())); - runtimeInfo.put("earliestMessageTimeStamp", String.valueOf(this.brokerController.getMessageStore().getEarliestMessageTime())); - runtimeInfo.put("startAcceptSendRequestTimeStamp", String.valueOf(this.brokerController.getBrokerConfig().getStartAcceptSendRequestTimeStamp())); - if (this.brokerController.getMessageStore() instanceof DefaultMessageStore) { - DefaultMessageStore defaultMessageStore = (DefaultMessageStore) this.brokerController.getMessageStore(); - runtimeInfo.put("remainTransientStoreBufferNumbs", String.valueOf(defaultMessageStore.remainTransientStoreBufferNumbs())); - if (defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) { - runtimeInfo.put("remainHowManyDataToCommit", MixAll.humanReadableByteCount(defaultMessageStore.getCommitLog().remainHowManyDataToCommit(), false)); - } - runtimeInfo.put("remainHowManyDataToFlush", MixAll.humanReadableByteCount(defaultMessageStore.getCommitLog().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.getUsableSpace(), false))); - } + runtimeInfo.put("EndTransactionQueueSize", String.valueOf(this.brokerController.getEndTransactionThreadPoolQueue().size())); + runtimeInfo.put("EndTransactionThreadPoolQueueCapacity", + String.valueOf(this.brokerController.getBrokerConfig().getEndTransactionPoolQueueCapacity())); return runtimeInfo; } @@ -1490,12 +2469,12 @@ private RemotingCommand callConsumer( } catch (RemotingTimeoutException e) { response.setCode(ResponseCode.CONSUME_MSG_TIMEOUT); response - .setRemark(String.format("consumer <%s> <%s> Timeout: %s", consumerGroup, clientId, RemotingHelper.exceptionSimpleDesc(e))); + .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, RemotingHelper.exceptionSimpleDesc(e))); + String.format("invoke consumer <%s> <%s> Exception: %s", consumerGroup, clientId, UtilAll.exceptionSimpleDesc(e))); return response; } } @@ -1507,7 +2486,7 @@ private RemotingCommand queryConsumeQueue(ChannelHandlerContext ctx, RemotingCommand response = RemotingCommand.createResponseCommand(null); - ConsumeQueue consumeQueue = this.brokerController.getMessageStore().getConsumeQueue(requestHeader.getTopic(), + ConsumeQueueInterface consumeQueue = this.brokerController.getMessageStore().getConsumeQueue(requestHeader.getTopic(), requestHeader.getQueueId()); if (consumeQueue == null) { response.setCode(ResponseCode.SYSTEM_ERROR); @@ -1538,26 +2517,31 @@ private RemotingCommand queryConsumeQueue(ChannelHandlerContext ctx, } } - SelectMappedBufferResult result = consumeQueue.getIndexBuffer(requestHeader.getIndex()); + 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<>(); - for (int i = 0; i < result.getSize() && i < requestHeader.getCount() * ConsumeQueue.CQ_STORE_UNIT_SIZE; i += ConsumeQueue.CQ_STORE_UNIT_SIZE) { + while (result.hasNext()) { + CqUnit cqUnit = result.next(); + if (cqUnit.getQueueOffset() - requestHeader.getIndex() >= requestHeader.getCount()) { + break; + } + ConsumeQueueData one = new ConsumeQueueData(); - one.setPhysicOffset(result.getByteBuffer().getLong()); - one.setPhysicSize(result.getByteBuffer().getInt()); - one.setTagsCode(result.getByteBuffer().getLong()); + one.setPhysicOffset(cqUnit.getPos()); + one.setPhysicSize(cqUnit.getSize()); + one.setTagsCode(cqUnit.getTagsCode()); - if (!consumeQueue.isExtAddr(one.getTagsCode())) { + if (cqUnit.getCqExtUnit() == null && cqUnit.isTagsCodeValid()) { queues.add(one); continue; } - ConsumeQueueExt.CqExtUnit cqExtUnit = consumeQueue.getExt(one.getTagsCode()); - if (cqExtUnit != null) { + 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()); @@ -1596,18 +2580,18 @@ private RemotingCommand resumeCheckHalfMessage(ChannelHandlerContext ctx, .putMessage(toMessageExtBrokerInner(msg)); if (putMessageResult != null && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) { - log.info( + 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 { - log.error("Put message back to RMQ_SYS_TRANS_HALF_TOPIC failed."); + 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) { - log.error("Exception was thrown when putting message back to RMQ_SYS_TRANS_HALF_TOPIC."); + 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 { @@ -1636,4 +2620,160 @@ private MessageExtBrokerInner toMessageExtBrokerInner(MessageExt msgExt) { 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().getTopicConfigTable().get(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 EpochEntryCache entryCache = new EpochEntryCache(brokerConfig.getBrokerClusterName(), + brokerConfig.getBrokerName(), brokerConfig.getBrokerId(), replicasManager.getEpochEntries(), this.brokerController.getMessageStore().getMaxPhyOffset()); + + final RemotingCommand response = RemotingCommand.createResponseCommand(null); + 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) { + replicasManager.changeBrokerRole(requestHeader.getMasterBrokerId(), requestHeader.getMasterAddress(), requestHeader.getMasterEpoch(), requestHeader.getSyncStateSetEpoch(), syncStateSetInfo.getSyncStateSet()); + } + response.setCode(ResponseCode.SUCCESS); + response.setRemark(null); + + return response; + } + + 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; + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java new file mode 100644 index 00000000000..2ccdf07f6aa --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java @@ -0,0 +1,245 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.fastjson.JSON; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.broker.metrics.PopMetricsManager; +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.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.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.ChangeInvisibleTimeRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeResponseHeader; +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.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 { + 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 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; + } + long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); + long maxOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); + if (requestHeader.getOffset() < minOffset || requestHeader.getOffset() > maxOffset) { + response.setCode(ResponseCode.NO_MESSAGE); + return response; + } + + String[] extraInfo = ExtraInfoUtil.split(requestHeader.getExtraInfo()); + + if (ExtraInfoUtil.isOrder(extraInfo)) { + return processChangeInvisibleTimeForOrder(requestHeader, extraInfo, response, responseHeader); + } + + // add new ck + long now = System.currentTimeMillis(); + PutMessageResult ckResult = appendCheckPoint(requestHeader, ExtraInfoUtil.getReviveQid(extraInfo), requestHeader.getQueueId(), requestHeader.getOffset(), now, ExtraInfoUtil.getBrokerName(extraInfo)); + + if (ckResult.getPutMessageStatus() != PutMessageStatus.PUT_OK + && ckResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT + && ckResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT + && ckResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) { + POP_LOGGER.error("change Invisible, put new ck error: {}", ckResult); + response.setCode(ResponseCode.SYSTEM_ERROR); + return response; + } + + // ack old msg. + try { + ackOrigin(requestHeader, extraInfo); + } catch (Throwable e) { + POP_LOGGER.error("change Invisible, put ack msg error: {}, {}", requestHeader.getExtraInfo(), e.getMessage()); + // cancel new ck? + } + + responseHeader.setInvisibleTime(requestHeader.getInvisibleTime()); + responseHeader.setPopTime(now); + responseHeader.setReviveQid(ExtraInfoUtil.getReviveQid(extraInfo)); + return 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 void 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; + } + + msgInner.setTopic(reviveTopic); + msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.charset)); + 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())); + PutMessageResult putMessageResult = this.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("change Invisible, put ack msg fail: {}, {}", ackMsg, putMessageResult); + } + PopMetricsManager.incPopReviveAckPutCount(ackMsg, putMessageResult.getPutMessageStatus()); + } + + private PutMessageResult appendCheckPoint(final ChangeInvisibleTimeRequestHeader requestHeader, int reviveQid, + int queueId, long offset, long popTime, String brokerName) { + // 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(brokerName); + + msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.charset)); + 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())); + PutMessageResult putMessageResult = this.brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); + + 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) { + PopMetricsManager.incPopReviveCkPutCount(ck, putMessageResult.getPutMessageStatus()); + if (putMessageResult.isOk()) { + this.brokerController.getBrokerStatsManager().incBrokerCkNums(1); + this.brokerController.getBrokerStatsManager().incGroupCkNums(requestHeader.getConsumerGroup(), requestHeader.getTopic(), 1); + } + } + + return putMessageResult; + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ClientManageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ClientManageProcessor.java index aa7d0a3a221..b51967e184f 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ClientManageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ClientManageProcessor.java @@ -16,6 +16,8 @@ */ 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; @@ -23,29 +25,30 @@ 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.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.body.CheckClientRequestBody; -import org.apache.rocketmq.common.protocol.header.UnregisterClientRequestHeader; -import org.apache.rocketmq.common.protocol.header.UnregisterClientResponseHeader; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData; -import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData; -import org.apache.rocketmq.common.protocol.heartbeat.ProducerData; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; -import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.common.sysflag.TopicSysFlag; import org.apache.rocketmq.filter.FilterFactory; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.AsyncNettyRequestProcessor; 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 extends AsyncNettyRequestProcessor implements NettyRequestProcessor { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); +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; @@ -81,41 +84,58 @@ public RemotingCommand heartBeat(ChannelHandlerContext ctx, RemotingCommand requ 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 (ConsumerData data : heartbeatData.getConsumerDataSet()) { - SubscriptionGroupConfig subscriptionGroupConfig = - this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig( - data.getGroupName()); - boolean isNotifyConsumerIdsChangedEnable = true; - if (null != subscriptionGroupConfig) { - isNotifyConsumerIdsChangedEnable = subscriptionGroupConfig.isNotifyConsumerIdsChangedEnable(); - int topicSysFlag = 0; - if (data.isUnitMode()) { - topicSysFlag = TopicSysFlag.buildSysFlag(false, true); + for (final SubscriptionData subscriptionData : consumerData.getSubscriptionDataSet()) { + if (this.brokerController.getTopicConfigManager().isOrderTopic(subscriptionData.getTopic())) { + hasOrderTopicSub = true; + break; } - String newTopic = MixAll.getRetryTopic(data.getGroupName()); - this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod( - newTopic, - subscriptionGroupConfig.getRetryQueueNums(), - PermName.PERM_WRITE | PermName.PERM_READ, topicSysFlag); } + 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( - data.getGroupName(), + consumerData.getGroupName(), clientChannelInfo, - data.getConsumeType(), - data.getMessageModel(), - data.getConsumeFromWhere(), - data.getSubscriptionDataSet(), + consumerData.getConsumeType(), + consumerData.getMessageModel(), + consumerData.getConsumeFromWhere(), + consumerData.getSubscriptionDataSet(), isNotifyConsumerIdsChangedEnable ); - if (changed) { - log.info("registerConsumer info changed {} {}", - data.toString(), - RemotingHelper.parseChannelRemoteAddr(ctx.channel()) - ); + LOGGER.info("ClientManageProcessor: registerConsumer info changed, SDK address={}, consumerData={}", + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), consumerData.toString()); } + } for (ProducerData data : heartbeatData.getProducerDataSet()) { @@ -124,6 +144,63 @@ public RemotingCommand heartBeat(ChannelHandlerContext ctx, RemotingCommand requ } 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; } @@ -190,7 +267,7 @@ public RemotingCommand checkClientConfig(ChannelHandlerContext ctx, RemotingComm try { FilterFactory.INSTANCE.get(subscriptionData.getExpressionType()).compile(subscriptionData.getSubString()); } catch (Exception e) { - log.warn("Client {}@{} filter message, but failed to compile expression! sub={}, error={}", + 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()); diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ConsumerManageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ConsumerManageProcessor.java index 77317a6ffe7..9c6d28d3dcf 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ConsumerManageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ConsumerManageProcessor.java @@ -20,27 +20,35 @@ 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.InternalLogger; -import org.apache.rocketmq.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupResponseBody; -import org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupResponseHeader; -import org.apache.rocketmq.common.protocol.header.QueryConsumerOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.QueryConsumerOffsetResponseHeader; -import org.apache.rocketmq.common.protocol.header.UpdateConsumerOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.UpdateConsumerOffsetResponseHeader; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.AsyncNettyRequestProcessor; 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; -public class ConsumerManageProcessor extends AsyncNettyRequestProcessor implements NettyRequestProcessor { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); +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) { @@ -89,11 +97,11 @@ public RemotingCommand getConsumerListByGroup(ChannelHandlerContext ctx, Remotin response.setRemark(null); return response; } else { - log.warn("getAllClientId failed, {} {}", requestHeader.getConsumerGroup(), + LOGGER.warn("getAllClientId failed, {} {}", requestHeader.getConsumerGroup(), RemotingHelper.parseChannelRemoteAddr(ctx.channel())); } } else { - log.warn("getConsumerGroupInfo failed, {} {}", requestHeader.getConsumerGroup(), + LOGGER.warn("getConsumerGroupInfo failed, {} {}", requestHeader.getConsumerGroup(), RemotingHelper.parseChannelRemoteAddr(ctx.channel())); } @@ -102,20 +110,192 @@ public RemotingCommand getConsumerListByGroup(ChannelHandlerContext ctx, Remotin 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.setBname(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); - this.brokerController.getConsumerOffsetManager().commitOffset(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.getConsumerGroup(), - requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getCommitOffset()); + (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.getTopicConfigManager().containsTopic(requestHeader.getTopic())) { + response.setCode(ResponseCode.TOPIC_NOT_EXIST); + response.setRemark("Topic " + topic + " not exist!"); + return response; + } + + if (queueId == null) { + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark("QueueId is null, topic is " + topic); + return response; + } + + if (offset == null) { + response.setCode(ResponseCode.SYSTEM_ERROR); + 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.setBname(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 = @@ -126,6 +306,12 @@ private RemotingCommand queryConsumerOffset(ChannelHandlerContext ctx, RemotingC (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()); @@ -138,9 +324,12 @@ private RemotingCommand queryConsumerOffset(ChannelHandlerContext ctx, RemotingC long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); - if (minOffset <= 0 - && !this.brokerController.getMessageStore().checkInDiskByConsumeOffset( - requestHeader.getTopic(), requestHeader.getQueueId(), 0)) { + 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); @@ -150,6 +339,11 @@ private RemotingCommand queryConsumerOffset(ChannelHandlerContext ctx, RemotingC } } + RemotingCommand rewriteResponseResult = rewriteResponseForStaticTopic(requestHeader, responseHeader, mappingContext, response.getCode()); + if (rewriteResponseResult != null) { + return rewriteResponseResult; + } + return response; } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/DefaultPullMessageResultHandler.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/DefaultPullMessageResultHandler.java new file mode 100644 index 00000000000..913e1a96c43 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/DefaultPullMessageResultHandler.java @@ -0,0 +1,297 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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) { + 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 = BrokerMetricsManager.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(); + BrokerMetricsManager.messagesOutTotal.add(getMessageResult.getMessageCount(), attributes); + BrokerMetricsManager.throughputOutTotal.add(getMessageResult.getBufferTotalSize(), attributes); + } + + if (!channelIsWritable(channel, requestHeader)) { + getMessageResult.release(); + //ignore pull request + return null; + } + + if (this.brokerController.getBrokerConfig().isTransferMsgByHeap()) { + + final long beginTimeMills = this.brokerController.getMessageStore().now(); + 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(); + 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.rpcLatency.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); + } + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/EndTransactionProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/EndTransactionProcessor.java index 41e7df307c9..f6aa0d48c9e 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/EndTransactionProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/EndTransactionProcessor.java @@ -17,33 +17,34 @@ 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.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader; +import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.sysflag.MessageSysFlag; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.AsyncNettyRequestProcessor; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; -import org.apache.rocketmq.store.MessageExtBrokerInner; +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; /** * EndTransaction processor: process commit and rollback message */ -public class EndTransactionProcessor extends AsyncNettyRequestProcessor implements NettyRequestProcessor { - private static final InternalLogger LOGGER = InternalLoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); +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) { @@ -55,7 +56,7 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final EndTransactionRequestHeader requestHeader = - (EndTransactionRequestHeader)request.decodeCommandCustomHeader(EndTransactionRequestHeader.class); + (EndTransactionRequestHeader) request.decodeCommandCustomHeader(EndTransactionRequestHeader.class); LOGGER.debug("Transaction request:{}", requestHeader); if (BrokerRole.SLAVE == brokerController.getMessageStoreConfig().getBrokerRole()) { response.setCode(ResponseCode.SLAVE_NOT_AVAILABLE); @@ -126,6 +127,12 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand 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()); @@ -145,6 +152,12 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand } 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) { this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage()); @@ -157,6 +170,30 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand return response; } + /** + * 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; @@ -231,27 +268,51 @@ private RemotingCommand sendFinalMessage(MessageExtBrokerInner msgInner) { response.setRemark(null); break; // Failed - case CREATE_MAPEDFILE_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("The message is illegal, maybe msg body or properties length not matched. msg body length limit 128k, msg properties length limit 32k."); + 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_PAGECACHE_BUSY: + 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"); diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/NotificationProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/NotificationProcessor.java new file mode 100644 index 00000000000..a1534038325 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/NotificationProcessor.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Objects; +import java.util.Random; +import org.apache.rocketmq.broker.BrokerController; +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.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.NotificationRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.NotificationResponseHeader; +import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; + +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); + } + + @Override + public boolean rejectRequest() { + return false; + } + + public void notifyMessageArriving(final String topic, final int queueId) { + popLongPollingService.notifyMessageArriving(topic, queueId); + } + + @Override + public RemotingCommand processRequest(final ChannelHandlerContext ctx, + RemotingCommand request) throws RemotingCommandException { + 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())); + } + Channel channel = ctx.channel(); + + RemotingCommand response = RemotingCommand.createResponseCommand(NotificationResponseHeader.class); + final NotificationResponseHeader responseHeader = (NotificationResponseHeader) response.readCustomHeader(); + final NotificationRequestHeader requestHeader = + (NotificationRequestHeader) request.decodeCommandCustomHeader(NotificationRequestHeader.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.SYSTEM_ERROR); + 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; + boolean needRetry = randomQ % 5 == 0; + if (needRetry) { + TopicConfig retryTopicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup())); + if (retryTopicConfig != null) { + for (int i = 0; i < retryTopicConfig.getReadQueueNums(); i++) { + int queueId = (randomQ + i) % retryTopicConfig.getReadQueueNums(); + hasMsg = hasMsgFromQueue(true, requestHeader, queueId); + if (hasMsg) { + break; + } + } + } + } + if (!hasMsg) { + if (requestHeader.getQueueId() < 0) { + // read all queue + for (int i = 0; i < topicConfig.getReadQueueNums(); i++) { + int queueId = (randomQ + i) % topicConfig.getReadQueueNums(); + hasMsg = hasMsgFromQueue(false, requestHeader, queueId); + if (hasMsg) { + break; + } + } + } else { + int queueId = requestHeader.getQueueId(); + hasMsg = hasMsgFromQueue(false, requestHeader, queueId); + } + // if it doesn't have message, fetch retry again + if (!needRetry && !hasMsg) { + TopicConfig retryTopicConfig = + this.brokerController.getTopicConfigManager().selectTopicConfig(KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup())); + if (retryTopicConfig != null) { + for (int i = 0; i < retryTopicConfig.getReadQueueNums(); i++) { + int queueId = (randomQ + i) % retryTopicConfig.getReadQueueNums(); + hasMsg = hasMsgFromQueue(true, requestHeader, queueId); + if (hasMsg) { + break; + } + } + } + } + } + + if (!hasMsg) { + if (popLongPollingService.polling(ctx, request, new PollingHeader(requestHeader)) == PollingResult.POLLING_SUC) { + return null; + } + } + response.setCode(ResponseCode.SUCCESS); + responseHeader.setHasMsg(hasMsg); + return response; + } + + private boolean hasMsgFromQueue(boolean isRetry, NotificationRequestHeader requestHeader, int queueId) { + if (Boolean.TRUE.equals(requestHeader.getOrder())) { + if (this.brokerController.getConsumerOrderInfoManager().checkBlock(requestHeader.getAttemptId(), requestHeader.getTopic(), requestHeader.getConsumerGroup(), queueId, 0)) { + return false; + } + } + String topic = isRetry ? KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup()) : requestHeader.getTopic(); + long offset = getPopOffset(topic, requestHeader.getConsumerGroup(), queueId); + long restNum = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - offset; + return restNum > 0; + } + + 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; + } + } + + public PopLongPollingService getPopLongPollingService() { + return popLongPollingService; + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PeekMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PeekMessageProcessor.java new file mode 100644 index 00000000000..a8358c4ffb0 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PeekMessageProcessor.java @@ -0,0 +1,297 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.metrics.BrokerMetricsManager; +import org.apache.rocketmq.broker.pagecache.ManyMessageTransfer; +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 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 { + 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.SYSTEM_ERROR); + 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(); + int commercialSizePerMsg = this.brokerController.getBrokerConfig().getCommercialSizePerMsg(); + GetMessageResult getMessageResult = new GetMessageResult(commercialSizePerMsg); + boolean needRetry = randomQ % 5 == 0; + long popTime = System.currentTimeMillis(); + long restNum = 0; + if (needRetry) { + TopicConfig retryTopicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup())); + 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())); + 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 long beginTimeMills = this.brokerController.getMessageStore().now(); + 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(); + 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.rpcLatency.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) { + String topic = isRetry ? KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup()) : requestHeader.getTopic(); + GetMessageResult getMessageTmpResult; + long offset = getPopOffset(topic, requestHeader.getConsumerGroup(), queueId); + restNum = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - offset + restNum; + 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 = BrokerMetricsManager.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(); + BrokerMetricsManager.messagesOutTotal.add(getMessageResult.getMessageCount(), attributes); + BrokerMetricsManager.throughputOutTotal.add(getMessageResult.getBufferTotalSize(), attributes); + } + + for (SelectMappedBufferResult mapedBuffer : getMessageTmpResult.getMessageMapedList()) { + getMessageResult.addMessage(mapedBuffer); + } + } + 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(); + } + +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PollingInfoProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PollingInfoProcessor.java new file mode 100644 index 00000000000..65a4d7d7851 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PollingInfoProcessor.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.SYSTEM_ERROR); + 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().get(key); + if (queue != null) { + responseHeader.setPollingNum(queue.size()); + } else { + responseHeader.setPollingNum(0); + } + response.setCode(ResponseCode.SUCCESS); + return response; + } + +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java new file mode 100644 index 00000000000..d7bc7c6946a --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -0,0 +1,866 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.fastjson.JSON; +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; +import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.broker.metrics.PopMetricsManager; +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; + +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; + } + 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 not exit topic {} in buffer!", topic); + iterator.remove(); + continue; + } + if (!brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().containsKey(cid)) { + POP_LOGGER.info("[PopBuffer]remove not exit sub {} 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 void scan() { + long startTime = System.currentTimeMillis(); + int count = 0, countCk = 0; + Iterator> iterator = buffer.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + PopCheckPointWrapper pointWrapper = entry.getValue(); + + // 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, false); + countCk++; + } + continue; + } else if (removeCk) { + // put buffer ak to store + if (pointWrapper.getReviveQueueOffset() < 0) { + putCkToStore(pointWrapper, false); + 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) { + if (putBatchAckToStore(pointWrapper, indexList)) { + count += indexList.size(); + for (Byte i : indexList) { + markBitCAS(pointWrapper.getToStoreBits(), i); + } + } + } + } 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)) { + if (putAckToStore(pointWrapper, i)) { + count++; + markBitCAS(pointWrapper.getToStoreBits(), i); + } + } + } + } + + if (isCkDoneForFinish(pointWrapper) && pointWrapper.isCkStored()) { + if (brokerController.getBrokerConfig().isEnablePopLog()) { + POP_LOGGER.info("[PopBuffer]ck finish, {}", pointWrapper); + } + iterator.remove(); + counter.decrementAndGet(); + continue; + } + } + } + + 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, countCk, counter.get(), offsetBufferSize); + this.serving = false; + } else { + if (scanTimes % countOfSecond1 == 0) { + POP_LOGGER.info("[PopBuffer]scan, PopBufferEclipse={}, " + + "PopBufferToStoreAck={}, PopBufferToStoreCk={}, PopBufferSize={}, PopBufferOffsetSize={}", + eclipse, count, countCk, counter.get(), offsetBufferSize); + } + } + PopMetricsManager.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 void addCkJustOffset(PopCheckPoint point, int reviveQueueId, long reviveQueueOffset, long nextBeginOffset) { + PopCheckPointWrapper pointWrapper = new PopCheckPointWrapper(reviveQueueId, reviveQueueOffset, point, nextBeginOffset, true); + + 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); + } + } + + 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; + } + + 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()); + PutMessageResult putMessageResult = brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); + PopMetricsManager.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) { + 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 boolean putAckToStore(final PopCheckPointWrapper pointWrapper, byte msgIndex) { + 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()); + msgInner.setTopic(popMessageProcessor.reviveTopic); + msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.charset)); + 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())); + PutMessageResult putMessageResult = brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); + PopMetricsManager.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 false; + } + if (brokerController.getBrokerConfig().isEnablePopLog()) { + POP_LOGGER.info("[PopBuffer]put ack to store ok: {}, {}, {}", pointWrapper, ackMsg, putMessageResult); + } + + return true; + } + + private boolean putBatchAckToStore(final PopCheckPointWrapper pointWrapper, final List msgIndexList) { + 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.reviveTopic); + msgInner.setBody(JSON.toJSONString(batchAckMsg).getBytes(DataConverter.charset)); + 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())); + 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]put batch ack to store fail: {}, {}, {}", pointWrapper, batchAckMsg, putMessageResult); + return false; + } + if (brokerController.getBrokerConfig().isEnablePopLog()) { + POP_LOGGER.info("[PopBuffer]put batch ack to store ok: {}, {}, {}", pointWrapper, batchAckMsg, putMessageResult); + } + + return true; + } + + 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.reviveTopic); + 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(); + } + } + +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopInflightMessageCounter.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopInflightMessageCounter.java new file mode 100644 index 00000000000..6749af3d750 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopInflightMessageCounter.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java new file mode 100644 index 00000000000..28549bfedc2 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -0,0 +1,873 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.fastjson.JSON; +import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; +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.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Objects; +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 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.metrics.BrokerMetricsManager; +import org.apache.rocketmq.broker.pagecache.ManyMessageTransfer; +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.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.DataConverter; +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.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.pop.AckMsg; +import org.apache.rocketmq.store.pop.BatchAckMsg; +import org.apache.rocketmq.store.pop.PopCheckPoint; + +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 final BrokerController brokerController; + private final Random random = new Random(System.currentTimeMillis()); + String reviveTopic; + private static final String BORN_TIME = "bornTime"; + + 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); + this.queueLockManager = new QueueLockManager(); + this.popBufferMergeService = new PopBufferMergeService(this.brokerController, this); + this.ckMessageNumber = new AtomicLong(); + } + + 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 ConcurrentLinkedHashMap> getPollingMap() { + return popLongPollingService.getPollingMap(); + } + + public void notifyLongPollingRequestIfNeed(String topic, String group, int queueId) { + 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, group, -1); + if (!notifySuccess) { + // notify pop queue + notifySuccess = popLongPollingService.notifyMessageArriving(topic, group, queueId); + } + 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) { + popLongPollingService.notifyMessageArriving(topic, queueId); + } + + public boolean notifyMessageArriving(final String topic, final String cid, final int queueId) { + return popLongPollingService.notifyMessageArriving(topic, cid, queueId); + } + + @Override + public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) + throws RemotingCommandException { + 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())); + } + Channel channel = ctx.channel(); + + RemotingCommand response = RemotingCommand.createResponseCommand(PopMessageResponseHeader.class); + final PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) response.readCustomHeader(); + final PopMessageRequestHeader requestHeader = + (PopMessageRequestHeader) request.decodeCommandCustomHeader(PopMessageRequestHeader.class); + StringBuilder startOffsetInfo = new StringBuilder(64); + StringBuilder msgOffsetInfo = new StringBuilder(64); + StringBuilder orderCountInfo = null; + if (requestHeader.isOrder()) { + orderCountInfo = new StringBuilder(64); + } + + brokerController.getConsumerManager().compensateBasicConsumerInfo(requestHeader.getConsumerGroup(), + ConsumeType.CONSUME_POP, MessageModel.CLUSTERING); + + response.setOpaque(request.getOpaque()); + + 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.SYSTEM_ERROR); + 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.SYSTEM_ERROR); + 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; + } + + ExpressionMessageFilter messageFilter = null; + if (requestHeader.getExp() != null && requestHeader.getExp().length() > 0) { + try { + SubscriptionData subscriptionData = FilterAPI.build(requestHeader.getTopic(), requestHeader.getExp(), requestHeader.getExpType()); + brokerController.getConsumerManager().compensateSubscribeData(requestHeader.getConsumerGroup(), + requestHeader.getTopic(), subscriptionData); + + String retryTopic = KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup()); + 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 { + SubscriptionData subscriptionData = FilterAPI.build(requestHeader.getTopic(), "*", ExpressionType.TAG); + brokerController.getConsumerManager().compensateSubscribeData(requestHeader.getConsumerGroup(), + requestHeader.getTopic(), subscriptionData); + + String retryTopic = KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup()); + 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()); + } + } + + 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()); + } + + int commercialSizePerMsg = this.brokerController.getBrokerConfig().getCommercialSizePerMsg(); + GetMessageResult getMessageResult = new GetMessageResult(commercialSizePerMsg); + ExpressionMessageFilter finalMessageFilter = messageFilter; + StringBuilder finalOrderCountInfo = orderCountInfo; + + boolean needRetry = randomQ % 5 == 0; + long popTime = System.currentTimeMillis(); + CompletableFuture getMessageFuture = CompletableFuture.completedFuture(0L); + if (needRetry && !requestHeader.isOrder()) { + TopicConfig retryTopicConfig = + this.brokerController.getTopicConfigManager().selectTopicConfig(KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup())); + if (retryTopicConfig != null) { + for (int i = 0; i < retryTopicConfig.getReadQueueNums(); i++) { + int queueId = (randomQ + i) % retryTopicConfig.getReadQueueNums(); + getMessageFuture = getMessageFuture.thenCompose(restNum -> popMsgFromQueue(requestHeader.getAttemptId(), true, getMessageResult, requestHeader, queueId, restNum, reviveQid, channel, popTime, finalMessageFilter, + startOffsetInfo, msgOffsetInfo, finalOrderCountInfo)); + } + } + } + if (requestHeader.getQueueId() < 0) { + // read all queue + for (int i = 0; i < topicConfig.getReadQueueNums(); i++) { + int queueId = (randomQ + i) % topicConfig.getReadQueueNums(); + getMessageFuture = getMessageFuture.thenCompose(restNum -> popMsgFromQueue(requestHeader.getAttemptId(), false, getMessageResult, requestHeader, queueId, restNum, reviveQid, channel, popTime, finalMessageFilter, + startOffsetInfo, msgOffsetInfo, finalOrderCountInfo)); + } + } else { + int queueId = requestHeader.getQueueId(); + getMessageFuture = getMessageFuture.thenCompose(restNum -> popMsgFromQueue(requestHeader.getAttemptId(), false, getMessageResult, requestHeader, queueId, restNum, reviveQid, channel, popTime, finalMessageFilter, + startOffsetInfo, msgOffsetInfo, finalOrderCountInfo)); + } + // if not full , fetch retry again + if (!needRetry && getMessageResult.getMessageMapedList().size() < requestHeader.getMaxMsgNums() && !requestHeader.isOrder()) { + TopicConfig retryTopicConfig = + this.brokerController.getTopicConfigManager().selectTopicConfig(KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup())); + if (retryTopicConfig != null) { + for (int i = 0; i < retryTopicConfig.getReadQueueNums(); i++) { + int queueId = (randomQ + i) % retryTopicConfig.getReadQueueNums(); + getMessageFuture = getMessageFuture.thenCompose(restNum -> popMsgFromQueue(requestHeader.getAttemptId(), true, getMessageResult, requestHeader, queueId, restNum, reviveQid, channel, popTime, finalMessageFilter, + startOffsetInfo, msgOffsetInfo, finalOrderCountInfo)); + } + } + } + + final RemotingCommand finalResponse = response; + getMessageFuture.thenApply(restNum -> { + 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.getConsumerGroup(), + requestHeader.getQueueId()); + } + } else { + PollingResult pollingResult = popLongPollingService.polling(ctx, request, new PollingHeader(requestHeader)); + if (PollingResult.POLLING_SUC == pollingResult) { + 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() && finalOrderCountInfo != null) { + responseHeader.setOrderCountInfo(finalOrderCountInfo.toString()); + } + finalResponse.setRemark(getMessageResult.getStatus().name()); + switch (finalResponse.getCode()) { + case ResponseCode.SUCCESS: + if (this.brokerController.getBrokerConfig().isTransferMsgByHeap()) { + final long beginTimeMills = this.brokerController.getMessageStore().now(); + 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(); + 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.rpcLatency.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)); + return null; + } + + private CompletableFuture popMsgFromQueue(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 topic = isRetry ? KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), + requestHeader.getConsumerGroup()) : requestHeader.getTopic(); + String lockKey = + topic + PopAckConstants.SPLIT + requestHeader.getConsumerGroup() + PopAckConstants.SPLIT + queueId; + boolean isOrder = requestHeader.isOrder(); + long offset = getPopOffset(topic, requestHeader.getConsumerGroup(), queueId, requestHeader.getInitMode(), + false, lockKey, false); + CompletableFuture future = new CompletableFuture<>(); + if (!queueLockManager.tryLock(lockKey)) { + restNum = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - offset + restNum; + future.complete(restNum); + return future; + } + + try { + future.whenComplete((result, throwable) -> queueLockManager.unLock(lockKey)); + offset = getPopOffset(topic, requestHeader.getConsumerGroup(), queueId, requestHeader.getInitMode(), + true, lockKey, true); + if (isOrder && brokerController.getConsumerOrderInfoManager().checkBlock(attemptId, topic, + requestHeader.getConsumerGroup(), queueId, requestHeader.getInvisibleTime())) { + future.complete(this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - offset + restNum); + return future; + } + + if (isOrder) { + 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) { + atomicRestNum.set(brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - atomicOffset.get() + atomicRestNum.get()); + 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 = BrokerMetricsManager.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(); + BrokerMetricsManager.messagesOutTotal.add(result.getMessageCount(), attributes); + BrokerMetricsManager.throughputOutTotal.add(result.getBufferTotalSize(), attributes); + + if (isOrder) { + this.brokerController.getConsumerOrderInfoManager().update(requestHeader.getAttemptId(), isRetry, topic, + requestHeader.getConsumerGroup(), + queueId, popTime, requestHeader.getInvisibleTime(), result.getMessageQueueOffset(), + orderCountInfo); + this.brokerController.getConsumerOffsetManager().commitOffset(channel.remoteAddress().toString(), + requestHeader.getConsumerGroup(), topic, queueId, finalOffset); + } else { + appendCheckPoint(requestHeader, topic, reviveQid, queueId, finalOffset, result, popTime, this.brokerController.getBrokerConfig().getBrokerName()); + } + ExtraInfoUtil.buildStartOffsetInfo(startOffsetInfo, isRetry, queueId, finalOffset); + ExtraInfoUtil.buildMsgOffsetInfo(msgOffsetInfo, isRetry, 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) { + popBufferMergeService.addCkMock(requestHeader.getConsumerGroup(), topic, queueId, finalOffset, + requestHeader.getInvisibleTime(), popTime, reviveQid, result.getNextBeginOffset(), brokerController.getBrokerConfig().getBrokerName()); +// this.brokerController.getConsumerOffsetManager().commitOffset(channel.remoteAddress().toString(), requestHeader.getConsumerGroup(), topic, +// queueId, getMessageTmpResult.getNextBeginOffset()); + } + + atomicRestNum.set(result.getMaxOffset() - result.getNextBeginOffset() + atomicRestNum.get()); + String brokerName = brokerController.getBrokerConfig().getBrokerName(); + for (SelectMappedBufferResult mapedBuffer : result.getMessageMapedList()) { + // We should not recode buffer for normal topic message + if (!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 long getPopOffset(String topic, String group, int queueId, int initMode, boolean init, String lockKey, + boolean checkResetOffset) { + + long offset = this.brokerController.getConsumerOffsetManager().queryOffset(group, topic, queueId); + if (offset < 0) { + if (ConsumeInitMode.MIN == initMode) { + offset = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, queueId); + } 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) { + this.brokerController.getConsumerOffsetManager().commitOffset( + "getPopOffset", group, topic, queueId, offset); + } + } + } + + 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 final MessageExtBrokerInner buildCkMsg(final PopCheckPoint ck, final int reviveQid) { + MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); + + msgInner.setTopic(reviveTopic); + msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.charset)); + 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 void 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)); + } + + final boolean addBufferSuc = this.popBufferMergeService.addCk( + ck, reviveQid, -1, getMessageTmpResult.getNextBeginOffset() + ); + + if (addBufferSuc) { + 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() { + this.lock = new AtomicBoolean(true); + this.lockTime = System.currentTimeMillis(); + } + + public boolean tryLock() { + boolean ret = lock.compareAndSet(true, false); + if (ret) { + this.lockTime = System.currentTimeMillis(); + return true; + } else { + return false; + } + } + + public void unLock() { + lock.set(true); + } + + 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 = expiredLocalCache.get(key); + + if (timedLock == null) { + TimedLock old = expiredLocalCache.putIfAbsent(key, new TimedLock()); + if (old != null) { + return false; + } else { + timedLock = expiredLocalCache.get(key); + } + } + + if (timedLock == null) { + return false; + } + + 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); + } + } + } + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java new file mode 100644 index 00000000000..93167db373a --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -0,0 +1,697 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.fastjson.JSON; +import io.opentelemetry.api.common.Attributes; +import java.nio.ByteBuffer; +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 org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.broker.metrics.BrokerMetricsManager; +import org.apache.rocketmq.broker.metrics.PopMetricsManager; +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.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.GetMessageStatus; +import org.apache.rocketmq.store.PutMessageResult; +import org.apache.rocketmq.store.pop.AckMsg; +import org.apache.rocketmq.store.pop.BatchAckMsg; +import org.apache.rocketmq.store.pop.PopCheckPoint; + +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 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())); + } else { + msgInner.setTopic(popCheckPoint.getTopic()); + } + msgInner.setBody(messageExt.getBody()); + msgInner.setQueueId(0); + 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()); + 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.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); + addRetryTopicIfNoExit(msgInner.getTopic(), popCheckPoint.getCId()); + PutMessageResult putMessageResult = brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); + PopMetricsManager.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()); + if (brokerController.getPopMessageProcessor() != null) { + brokerController.getPopMessageProcessor().notifyMessageArriving( + KeyBuilder.parseNormalTopic(popCheckPoint.getTopic(), popCheckPoint.getCId()), + popCheckPoint.getCId(), + -1 + ); + brokerController.getNotificationProcessor().notifyMessageArriving( + KeyBuilder.parseNormalTopic(popCheckPoint.getTopic(), popCheckPoint.getCId()), -1); + } + return true; + } + + private void initPopRetryOffset(String topic, String consumerGroup) { + long offset = this.brokerController.getConsumerOffsetManager().queryOffset(consumerGroup, topic, 0); + if (offset < 0) { + this.brokerController.getConsumerOffsetManager().commitOffset("initPopRetryOffset", consumerGroup, topic, + 0, 0); + } + } + + private void addRetryTopicIfNoExit(String topic, String consumerGroup) { + if (brokerController != null) { + TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(topic); + if (topicConfig != null) { + return; + } + topicConfig = new TopicConfig(topic); + topicConfig.setReadQueueNums(PopAckConstants.retryQueueNum); + topicConfig.setWriteQueueNums(PopAckConstants.retryQueueNum); + topicConfig.setTopicFilterType(TopicFilterType.SINGLE_TAG); + topicConfig.setPerm(6); + topicConfig.setTopicSysFlag(0); + brokerController.getTopicConfigManager().updateTopicConfig(topicConfig); + + initPopRetryOffset(topic, consumerGroup); + } + } + + 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(); + } + + private CompletableFuture> getBizMessage(String topic, long offset, int queueId, + String brokerName) { + return this.brokerController.getEscapeBridge().getMessageAsync(topic, offset, queueId, brokerName, 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 = BrokerMetricsManager.newAttributesBuilder() + .put(LABEL_TOPIC, topic) + .put(LABEL_CONSUMER_GROUP, group) + .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(topic) || MixAll.isSysConsumerGroup(group)) + .build(); + BrokerMetricsManager.messagesOutTotal.add(getMessageResult.getMessageCount(), attributes); + BrokerMetricsManager.throughputOutTotal.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 { + 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); + } + 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.info("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); + 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); + PopMetricsManager.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(), DataConverter.charset); + if (brokerController.getBrokerConfig().isEnablePopLog()) { + POP_LOGGER.info("reviveQueueId={},find ack, offset:{}, raw : {}", messageExt.getQueueId(), messageExt.getQueueOffset(), raw); + } + AckMsg ackMsg = JSON.parseObject(raw, AckMsg.class); + PopMetricsManager.incPopReviveAckGetCount(ackMsg, queueId); + String mergeKey = ackMsg.getTopic() + ackMsg.getConsumerGroup() + ackMsg.getQueueId() + ackMsg.getStartOffset() + ackMsg.getPopTime(); + 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(), DataConverter.charset); + 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); + PopMetricsManager.incPopReviveAckGetCount(bAckMsg, queueId); + String mergeKey = bAckMsg.getTopic() + bAckMsg.getConsumerGroup() + bAckMsg.getQueueId() + bAckMsg.getStartOffset() + bAckMsg.getPopTime(); + 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); + } + } + + 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.getTopic(), msgOffset, popCheckPoint.getQueueId(), popCheckPoint.getBrokerName()) + .thenApply(resultPair -> { + GetMessageStatus getMessageStatus = resultPair.getObject1(); + MessageExt message = resultPair.getObject2(); + if (message == null) { + POP_LOGGER.warn("reviveQueueId={}, can not get biz msg topic is {}, offset is {}, then continue", + queueId, popCheckPoint.getTopic(), msgOffset); + switch (getMessageStatus) { + case MESSAGE_WAS_REMOVING: + case OFFSET_TOO_SMALL: + case NO_MATCHED_LOGIC_QUEUE: + case NO_MESSAGE_IN_QUEUE: + return new Pair<>(msgOffset, true); + default: + return new Pair<>(msgOffset, false); + + } + } + //skip ck from last epoch + if (popCheckPoint.getPopTime() < message.getStoreTimestamp()) { + POP_LOGGER.warn("reviveQueueId={}, skip ck from last epoch {}", queueId, popCheckPoint); + return new Pair<>(msgOffset, true); + } + 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) { + 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.addDiff(0); + MessageExtBrokerInner ckMsg = brokerController.getPopMessageProcessor().buildCkMsg(newCk, queueId); + brokerController.getMessageStore().putMessage(ckMsg); + } + + public long getReviveBehindMillis() { + 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() { + if (currentReviveMessageTimestamp <= 0) { + return 0; + } + long diff = brokerController.getMessageStore().getMaxOffsetInQueue(reviveTopic, queueId) - reviveOffset; + 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; + } + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PullMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PullMessageProcessor.java index 20665a3c9f9..b2794b1289b 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PullMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PullMessageProcessor.java @@ -17,14 +17,14 @@ package org.apache.rocketmq.broker.processor; import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.FileRegion; -import java.nio.ByteBuffer; import java.util.List; +import java.util.Objects; 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; @@ -32,66 +32,274 @@ 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.pagecache.ManyMessageTransfer; +import org.apache.rocketmq.broker.plugin.PullMessageResultHandler; 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.constant.PermName; import org.apache.rocketmq.common.filter.ExpressionType; -import org.apache.rocketmq.common.filter.FilterAPI; import org.apache.rocketmq.common.help.FAQUrl; -import org.apache.rocketmq.common.message.MessageDecoder; -import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.PullMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.PullMessageResponseHeader; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; -import org.apache.rocketmq.common.protocol.topic.OffsetMovedEvent; -import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig; -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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.common.RemotingUtil; import org.apache.rocketmq.remoting.exception.RemotingCommandException; -import org.apache.rocketmq.remoting.netty.AsyncNettyRequestProcessor; +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.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.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.MessageExtBrokerInner; +import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageFilter; -import org.apache.rocketmq.store.PutMessageResult; +import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.stats.BrokerStatsManager; -public class PullMessageProcessor extends AsyncNettyRequestProcessor implements NettyRequestProcessor { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); - private final BrokerController brokerController; +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.setBname(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); + 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) + 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 = @@ -99,11 +307,21 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re response.setOpaque(request.getOpaque()); - log.debug("receive PullMessage request command, {}", request); + LOGGER.debug("receive PullMessage request command, {}", request); if (!PermName.isReadable(this.brokerController.getBrokerConfig().getBrokerPermission())) { response.setCode(ResponseCode.NO_PERMISSION); - response.setRemark(String.format("the broker[%s] pulling message is forbidden", this.brokerController.getBrokerConfig().getBrokerIP1())); + 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; } @@ -117,19 +335,14 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re if (!subscriptionGroupConfig.isConsumeEnable()) { response.setCode(ResponseCode.NO_PERMISSION); + responseHeader.setForbiddenType(ForbiddenType.GROUP_FORBIDDEN); response.setRemark("subscription group no permission, " + requestHeader.getConsumerGroup()); return response; } - final boolean hasSuspendFlag = PullSysFlag.hasSuspendFlag(requestHeader.getSysFlag()); - final boolean hasCommitOffsetFlag = PullSysFlag.hasCommitOffsetFlag(requestHeader.getSysFlag()); - final boolean hasSubscriptionFlag = PullSysFlag.hasSubscriptionFlag(requestHeader.getSysFlag()); - - final long suspendTimeoutMillisLong = hasSuspendFlag ? requestHeader.getSuspendTimeoutMillis() : 0; - TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (null == topicConfig) { - log.error("the topic {} not exist, consumer: {}", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel)); + 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; @@ -137,26 +350,52 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re 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()); - log.warn(errorInfo); + LOGGER.warn(errorInfo); response.setCode(ResponseCode.SYSTEM_ERROR); 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(), @@ -165,7 +404,7 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re assert consumerFilterData != null; } } catch (Exception e) { - log.warn("Parse the consumer's subscription[{}] failed, group: {}", requestHeader.getSubscription(), + 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"); @@ -175,7 +414,7 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo(requestHeader.getConsumerGroup()); if (null == consumerGroupInfo) { - log.warn("the consumer's group info not exist, group: {}", requestHeader.getConsumerGroup()); + 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; @@ -184,20 +423,30 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re 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) { - log.warn("the consumer's subscription not exist, group: {}, topic:{}", requestHeader.getConsumerGroup(), requestHeader.getTopic()); + 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()) { - log.warn("The broker's subscription is not latest, group: {} {}", requestHeader.getConsumerGroup(), + 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"); @@ -212,7 +461,7 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re return response; } if (consumerFilterData.getClientVersion() < requestHeader.getSubVersion()) { - log.warn("The broker's consumer filter data is not latest, group: {}, topic: {}, serverV: {}, clientV: {}", + 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"); @@ -237,348 +486,317 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re this.brokerController.getConsumerFilterManager()); } - final GetMessageResult getMessageResult = - this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup(), requestHeader.getTopic(), - requestHeader.getQueueId(), requestHeader.getQueueOffset(), requestHeader.getMaxMsgNums(), messageFilter); - if (getMessageResult != null) { - response.setRemark(getMessageResult.getStatus().name()); - responseHeader.setNextBeginOffset(getMessageResult.getNextBeginOffset()); - responseHeader.setMinOffset(getMessageResult.getMinOffset()); - responseHeader.setMaxOffset(getMessageResult.getMaxOffset()); - - if (getMessageResult.isSuggestPullingFromSlave()) { - responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly()); - } else { - responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID); - } - - switch (this.brokerController.getMessageStoreConfig().getBrokerRole()) { - case ASYNC_MASTER: - case SYNC_MASTER: - break; - case SLAVE: - if (!this.brokerController.getBrokerConfig().isSlaveReadEnable()) { - response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY); - responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID); + 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); } - break; - } - - if (this.brokerController.getBrokerConfig().isSlaveReadEnable()) { - // 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); } + } - switch (getMessageResult.getStatus()) { - case FOUND: - response.setCode(ResponseCode.SUCCESS); - break; - case MESSAGE_WAS_REMOVING: - 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 - log.info("the broker store no queue data, fix the request offset {} to {}, Topic: {} QueueId: {} Consumer Group: {}", - requestHeader.getQueueOffset(), - getMessageResult.getNextBeginOffset(), - requestHeader.getTopic(), - requestHeader.getQueueId(), - requestHeader.getConsumerGroup() + final boolean useResetOffsetFeature = brokerController.getBrokerConfig().isUseServerSideResetOffset(); + String topic = requestHeader.getTopic(); + 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)); + getMessageResult.setMaxOffset(messageStore.getMaxOffsetInQueue(topic, queueId)); + 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; + messageStore.getMessageAsync(group, topic, 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 ); - } else { - response.setCode(ResponseCode.PULL_NOT_FOUND); - } - break; - case NO_MATCHED_MESSAGE: - response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY); - break; - case OFFSET_FOUND_NULL: - response.setCode(ResponseCode.PULL_NOT_FOUND); - break; - case OFFSET_OVERFLOW_BADLY: - response.setCode(ResponseCode.PULL_OFFSET_MOVED); - // XXX: warn and notify me - log.info("the request offset: {} over flow badly, broker max offset: {}, consumer: {}", - requestHeader.getQueueOffset(), getMessageResult.getMaxOffset(), channel.remoteAddress()); - break; - case OFFSET_OVERFLOW_ONE: - response.setCode(ResponseCode.PULL_NOT_FOUND); - break; - case OFFSET_TOO_SMALL: - response.setCode(ResponseCode.PULL_OFFSET_MOVED); - log.info("the request offset too small. group={}, topic={}, requestOffset={}, brokerMinOffset={}, clientIp={}", - requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueOffset(), - getMessageResult.getMinOffset(), channel.remoteAddress()); - break; - default: - assert false; - break; + }) + .thenAccept(result -> NettyRemotingAbstract.writeResponse(channel, request, result)); } + } - if (this.hasConsumeMessageHook()) { - ConsumeMessageContext context = new ConsumeMessageContext(); - context.setConsumerGroup(requestHeader.getConsumerGroup()); - context.setTopic(requestHeader.getTopic()); - context.setQueueId(requestHeader.getQueueId()); - - String owner = request.getExtFields().get(BrokerStatsManager.COMMERCIAL_OWNER); + if (getMessageResult != null) { - switch (response.getCode()) { - case ResponseCode.SUCCESS: - int commercialBaseCount = brokerController.getBrokerConfig().getCommercialBaseCount(); - int incValue = getMessageResult.getMsgCount4Commercial() * commercialBaseCount; + return this.pullMessageResultHandler.handle( + getMessageResult, + request, + requestHeader, + channel, + subscriptionData, + subscriptionGroupConfig, + brokerAllowSuspend, + messageFilter, + response, + mappingContext + ); + } + return null; + } - context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_SUCCESS); - context.setCommercialRcvTimes(incValue); - context.setCommercialRcvSize(getMessageResult.getBufferTotalSize()); - context.setCommercialOwner(owner); + public boolean hasConsumeMessageHook() { + return consumeMessageHookList != null && !this.consumeMessageHookList.isEmpty(); + } - break; - case ResponseCode.PULL_NOT_FOUND: - if (!brokerAllowSuspend) { + /** + * 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; + } - context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_EPOLLS); - context.setCommercialRcvTimes(1); - context.setCommercialOwner(owner); + 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); + } - } - break; - case ResponseCode.PULL_RETRY_IMMEDIATELY: - case ResponseCode.PULL_OFFSET_MOVED: - context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_EPOLLS); - context.setCommercialRcvTimes(1); - context.setCommercialOwner(owner); - break; - default: - assert false; - break; + 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); } - - this.executeConsumeMessageHookBefore(context); } + } - switch (response.getCode()) { - case ResponseCode.SUCCESS: + } - this.brokerController.getBrokerStatsManager().incGroupGetNums(requestHeader.getConsumerGroup(), requestHeader.getTopic(), - getMessageResult.getMessageCount()); + 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())); + + switch (responseCode) { + case ResponseCode.SUCCESS: + int commercialBaseCount = brokerController.getBrokerConfig().getCommercialBaseCount(); + int incValue = getMessageResult.getMsgCount4Commercial() * commercialBaseCount; - this.brokerController.getBrokerStatsManager().incGroupGetSize(requestHeader.getConsumerGroup(), requestHeader.getTopic(), - getMessageResult.getBufferTotalSize()); + context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_SUCCESS); + context.setCommercialRcvTimes(incValue); + context.setCommercialRcvSize(getMessageResult.getBufferTotalSize()); + context.setCommercialOwner(owner); - this.brokerController.getBrokerStatsManager().incBrokerGetNums(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 { - try { - FileRegion fileRegion = - new ManyMessageTransfer(response.encodeHeader(getMessageResult.getBufferTotalSize()), getMessageResult); - channel.writeAndFlush(fileRegion).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - getMessageResult.release(); - if (!future.isSuccess()) { - log.error("transfer many message by pagecache failed, {}", channel.remoteAddress(), future.cause()); - } - } - }); - } catch (Throwable e) { - log.error("transfer many message by pagecache exception", e); - getMessageResult.release(); - } + context.setRcvStat(BrokerStatsManager.StatsType.RCV_SUCCESS); + context.setRcvMsgNum(getMessageResult.getMessageCount()); + context.setRcvMsgSize(getMessageResult.getBufferTotalSize()); + context.setCommercialRcvMsgNum(getMessageResult.getMsgCount4Commercial()); - response = null; - } break; case ResponseCode.PULL_NOT_FOUND: + if (!brokerAllowSuspend) { - if (brokerAllowSuspend && hasSuspendFlag) { - long pollingTimeMills = suspendTimeoutMillisLong; - if (!this.brokerController.getBrokerConfig().isLongPollingEnable()) { - pollingTimeMills = this.brokerController.getBrokerConfig().getShortPollingTimeMills(); - } + context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_EPOLLS); + context.setCommercialRcvTimes(1); + context.setCommercialOwner(owner); - 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); - response = null; - break; + context.setRcvStat(BrokerStatsManager.StatsType.RCV_EPOLLS); + context.setRcvMsgNum(0); + context.setRcvMsgSize(0); + context.setCommercialRcvMsgNum(0); } - - case ResponseCode.PULL_RETRY_IMMEDIATELY: break; + case ResponseCode.PULL_RETRY_IMMEDIATELY: 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()); - this.generateOffsetMovedEvent(event); - 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()); - } - + 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; } - } else { - response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark("store getMessage return null"); - } - - boolean storeOffsetEnable = brokerAllowSuspend; - storeOffsetEnable = storeOffsetEnable && hasCommitOffsetFlag; - storeOffsetEnable = storeOffsetEnable - && this.brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE; - if (storeOffsetEnable) { - this.brokerController.getConsumerOffsetManager().commitOffset(RemotingHelper.parseChannelRemoteAddr(channel), - requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getCommitOffset()); - } - return response; - } - public boolean hasConsumeMessageHook() { - return consumeMessageHookList != null && !this.consumeMessageHookList.isEmpty(); - } - - public void executeConsumeMessageHookBefore(final ConsumeMessageContext context) { - if (hasConsumeMessageHook()) { for (ConsumeMessageHook hook : this.consumeMessageHookList) { try { hook.consumeMessageBefore(context); - } catch (Throwable e) { + } catch (Throwable ignored) { } } } } - 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); - 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 tryCommitOffset(boolean brokerAllowSuspend, PullMessageRequestHeader requestHeader, + long nextOffset, String clientAddress) { + this.brokerController.getConsumerOffsetManager().commitPullOffset(clientAddress, + requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), nextOffset); - private 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(RemotingUtil.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); + 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) throws RemotingCommandException { - Runnable run = new Runnable() { - @Override - public void run() { - try { - final RemotingCommand response = PullMessageProcessor.this.processRequest(channel, request, false); - - if (response != null) { - response.setOpaque(request.getOpaque()); - response.markResponseType(); - try { - channel.writeAndFlush(response).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (!future.isSuccess()) { - log.error("processRequestWrapper response to {} failed", - future.channel().remoteAddress(), future.cause()); - log.error(request.toString()); - log.error(response.toString()); - } - } - }); - } catch (Throwable e) { - log.error("processRequestWrapper process request over, but response failed", e); - log.error(request.toString()); - log.error(response.toString()); - } + 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()); + } + }); + } catch (Throwable e) { + LOGGER.error("processRequestWrapper process request over, but response failed", e); + LOGGER.error(request.toString()); + LOGGER.error(response.toString()); } - } catch (RemotingCommandException e1) { - log.error("excuteRequestWhenWakeup run", e1); } + } catch (RemotingCommandException e1) { + LOGGER.error("excuteRequestWhenWakeup run", e1); } }; this.brokerController.getPullMessageExecutor().submit(new RequestTask(run, channel, request)); @@ -587,4 +805,78 @@ public void operationComplete(ChannelFuture future) throws Exception { 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) { + + 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(); + } + + return this.brokerController.getBroadcastOffsetManager() + .queryInitOffset(topic, group, queueId, clientId, requestHeader.getQueueOffset(), proxyPullBroadcast); + } + return -1L; + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessor.java new file mode 100644 index 00000000000..d55f1b5b7fb --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessor.java @@ -0,0 +1,324 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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; + +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 = 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; + } + + final String consumerGroup = requestBody.getConsumerGroup(); + + this.messageRequestModeManager.setMessageRequestMode(topic, consumerGroup, requestBody); + this.messageRequestModeManager.persist(); + + response.setCode(ResponseCode.SUCCESS); + response.setRemark(null); + return response; + } + + public MessageRequestModeManager getMessageRequestModeManager() { + return messageRequestModeManager; + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryMessageProcessor.java index 0f8d64c8438..38385149700 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryMessageProcessor.java @@ -16,32 +16,37 @@ */ package org.apache.rocketmq.broker.processor; -import io.netty.channel.ChannelFuture; 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.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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.QueryMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.QueryMessageResponseHeader; -import org.apache.rocketmq.common.protocol.header.ViewMessageRequestHeader; +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.AsyncNettyRequestProcessor; +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; -public class QueryMessageProcessor extends AsyncNettyRequestProcessor implements NettyRequestProcessor { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); +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) { @@ -102,17 +107,22 @@ public RemotingCommand queryMessage(ChannelHandlerContext ctx, RemotingCommand r FileRegion fileRegion = new QueryMessageTransfer(response.encodeHeader(queryMessageResult .getBufferTotalSize()), queryMessageResult); - ctx.channel().writeAndFlush(fileRegion).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { + ctx.channel() + .writeAndFlush(fileRegion) + .addListener((ChannelFutureListener) future -> { queryMessageResult.release(); + 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.rpcLatency.record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributes); if (!future.isSuccess()) { - log.error("transfer query message by page cache failed, ", future.cause()); + LOGGER.error("transfer query message by page cache failed, ", future.cause()); } - } - }); + }); } catch (Throwable e) { - log.error("", e); + LOGGER.error("", e); queryMessageResult.release(); } @@ -142,17 +152,22 @@ public RemotingCommand viewMessageById(ChannelHandlerContext ctx, RemotingComman FileRegion fileRegion = new OneMessageTransfer(response.encodeHeader(selectMappedBufferResult.getSize()), selectMappedBufferResult); - ctx.channel().writeAndFlush(fileRegion).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { + ctx.channel() + .writeAndFlush(fileRegion) + .addListener((ChannelFutureListener) future -> { selectMappedBufferResult.release(); + 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.rpcLatency.record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributes); if (!future.isSuccess()) { - log.error("Transfer one message from page cache failed, ", future.cause()); + LOGGER.error("Transfer one message from page cache failed, ", future.cause()); } - } - }); + }); } catch (Throwable e) { - log.error("", e); + LOGGER.error("", e); selectMappedBufferResult.release(); } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java index f31576fe37a..b2db356c8a4 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java @@ -19,36 +19,43 @@ 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.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.ReplyMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeaderV2; -import org.apache.rocketmq.common.protocol.header.SendMessageResponseHeader; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; -import org.apache.rocketmq.store.MessageExtBrokerInner; +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 java.util.concurrent.ThreadLocalRandom; +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 implements NettyRequestProcessor { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); +public class ReplyMessageProcessor extends AbstractSendMessageProcessor { + private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); public ReplyMessageProcessor(final BrokerController brokerController) { super(brokerController); @@ -63,8 +70,8 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, return null; } - mqtraceContext = buildMsgContext(ctx, requestHeader); - this.executeSendMessageHookBefore(ctx, request, mqtraceContext); + mqtraceContext = buildMsgContext(ctx, requestHeader, request); + this.executeSendMessageHookBefore(mqtraceContext); RemotingCommand response = this.processReplyMessageRequest(ctx, request, mqtraceContext, requestHeader); @@ -116,7 +123,7 @@ private RemotingCommand processReplyMessageRequest(final ChannelHandlerContext c } response.setCode(-1); - super.msgCheck(ctx, requestHeader, response); + super.msgCheck(ctx, requestHeader, request, response); if (response.getCode() != -1) { return response; } @@ -147,7 +154,7 @@ private RemotingCommand processReplyMessageRequest(final ChannelHandlerContext c if (this.brokerController.getBrokerConfig().isStoreReplyMessageEnable()) { PutMessageResult putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner); - this.handlePutMessageResult(putMessageResult, request, msgInner, responseHeader, sendMessageContext, queueIdInt); + this.handlePutMessageResult(putMessageResult, request, msgInner, responseHeader, sendMessageContext, queueIdInt, BrokerMetricsManager.getMessageType(requestHeader)); } return response; @@ -157,8 +164,10 @@ private PushReplyResult pushReplyMessage(final ChannelHandlerContext ctx, final SendMessageRequestHeader requestHeader, final Message msg) { ReplyMessageRequestHeader replyMessageRequestHeader = new ReplyMessageRequestHeader(); - replyMessageRequestHeader.setBornHost(ctx.channel().remoteAddress().toString()); - replyMessageRequestHeader.setStoreHost(this.getStoreHost().toString()); + 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()); @@ -235,7 +244,7 @@ private void handlePushReplyResult(PushReplyResult pushReplyResult, final Remoti private void handlePutMessageResult(PutMessageResult putMessageResult, final RemotingCommand request, final MessageExt msg, final SendMessageResponseHeader responseHeader, SendMessageContext sendMessageContext, - int queueIdInt) { + int queueIdInt, TopicMessageType messageType) { if (putMessageResult == null) { log.warn("process reply message, store putMessage return null"); return; @@ -252,23 +261,24 @@ private void handlePutMessageResult(PutMessageResult putMessageResult, break; // Failed - case CREATE_MAPEDFILE_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 properties length limit 32k."); + "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 body or properties length not matched. msg body length limit 128k."); + "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_PAGECACHE_BUSY: + case OS_PAGE_CACHE_BUSY: log.warn("[PC_SYNCHRONIZED]broker busy, start flow control for a while"); break; case UNKNOWN_ERROR: @@ -280,11 +290,23 @@ private void handlePutMessageResult(PutMessageResult putMessageResult, } 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(putMessageResult.getAppendMessageResult().getMsgNum()); + this.brokerController.getBrokerStatsManager().incBrokerPutNums(msg.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum()); + + if (!BrokerMetricsManager.isRetryOrDlqTopic(msg.getTopic())) { + Attributes attributes = BrokerMetricsManager.newAttributesBuilder() + .put(LABEL_TOPIC, msg.getTopic()) + .put(LABEL_MESSAGE_TYPE, messageType.getMetricsValue()) + .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(msg.getTopic())) + .build(); + BrokerMetricsManager.messagesInTotal.add(putMessageResult.getAppendMessageResult().getMsgNum(), attributes); + BrokerMetricsManager.throughputInTotal.add(putMessageResult.getAppendMessageResult().getWroteBytes(), attributes); + BrokerMetricsManager.messageSize.record(putMessageResult.getAppendMessageResult().getWroteBytes() / putMessageResult.getAppendMessageResult().getMsgNum(), attributes); + } responseHeader.setMsgId(putMessageResult.getAppendMessageResult().getMsgId()); responseHeader.setQueueId(queueIdInt); @@ -297,7 +319,7 @@ private void handlePutMessageResult(PutMessageResult putMessageResult, int commercialBaseCount = brokerController.getBrokerConfig().getCommercialBaseCount(); int wroteSize = putMessageResult.getAppendMessageResult().getWroteBytes(); - int incValue = (int) Math.ceil(wroteSize / BrokerStatsManager.SIZE_PER_COUNT) * commercialBaseCount; + int incValue = (int) Math.ceil(wroteSize * 1.0 / commercialSizePerMsg) * commercialBaseCount; sendMessageContext.setCommercialSendStats(BrokerStatsManager.StatsType.SEND_SUCCESS); sendMessageContext.setCommercialSendTimes(incValue); @@ -307,7 +329,7 @@ private void handlePutMessageResult(PutMessageResult putMessageResult, } else { if (hasSendMessageHook()) { int wroteSize = request.getBody().length; - int incValue = (int) Math.ceil(wroteSize / BrokerStatsManager.SIZE_PER_COUNT); + int incValue = (int) Math.ceil(wroteSize * 1.0 / commercialSizePerMsg); sendMessageContext.setCommercialSendStats(BrokerStatsManager.StatsType.SEND_FAILURE); sendMessageContext.setCommercialSendTimes(incValue); diff --git a/logging/src/main/java/org/apache/rocketmq/logging/package-info.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageCallback.java similarity index 63% rename from logging/src/main/java/org/apache/rocketmq/logging/package-info.java rename to broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageCallback.java index 7cb0645de0c..a6019dcc734 100644 --- a/logging/src/main/java/org/apache/rocketmq/logging/package-info.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageCallback.java @@ -15,21 +15,17 @@ * limitations under the License. */ -package org.apache.rocketmq.logging; +package org.apache.rocketmq.broker.processor; -/* - This package is a minimal logger on the basis of Apache Log4j without - file configuration and pattern layout configuration. Main forked files are - followed as below: - 1. LoggingEvent - 2. Logger - 3. Layout - 4. Level - 5. AsyncAppender - 6. FileAppender - 7. RollingFileAppender - 8. DailyRollingFileAppender - 9. ConsoleAppender +import org.apache.rocketmq.broker.mqtrace.SendMessageContext; +import org.apache.rocketmq.remoting.protocol.RemotingCommand; - For more information about Apache Log4j, please go to https://github.com/apache/log4j. - */ \ No newline at end of file +public interface SendMessageCallback { + /** + * On send complete. + * + * @param ctx send context + * @param response send response + */ + void onComplete(SendMessageContext ctx, RemotingCommand response); +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageProcessor.java index c8ea4d3b5a9..9625689a8ee 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageProcessor.java @@ -16,55 +16,64 @@ */ package org.apache.rocketmq.broker.processor; -import java.net.SocketAddress; -import java.util.List; +import io.netty.channel.ChannelHandlerContext; +import io.opentelemetry.api.common.Attributes; +import java.nio.ByteBuffer; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ThreadLocalRandom; - -import io.netty.channel.ChannelHandlerContext; +import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; -import org.apache.rocketmq.broker.mqtrace.ConsumeMessageContext; -import org.apache.rocketmq.broker.mqtrace.ConsumeMessageHook; +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.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.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.protocol.NamespaceUtil; -import org.apache.rocketmq.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageResponseHeader; -import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig; +import org.apache.rocketmq.common.message.MessageExtBrokerInner; 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.common.utils.CleanupPolicyUtils; +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.netty.RemotingResponseCallback; 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.MessageExtBrokerInner; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; -import org.apache.rocketmq.store.config.MessageStoreConfig; +import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.StorePathConfigHelper; import org.apache.rocketmq.store.stats.BrokerStatsManager; -public class SendMessageProcessor extends AbstractSendMessageProcessor implements NettyRequestProcessor { +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; - private List consumeMessageHookList; +public class SendMessageProcessor extends AbstractSendMessageProcessor implements NettyRequestProcessor { public SendMessageProcessor(final BrokerController brokerController) { super(brokerController); @@ -72,279 +81,92 @@ public SendMessageProcessor(final BrokerController brokerController) { @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, - RemotingCommand request) throws RemotingCommandException { - RemotingCommand response = null; - try { - response = asyncProcessRequest(ctx, request).get(); - } catch (InterruptedException | ExecutionException e) { - log.error("process SendMessage error, request : " + request.toString(), e); - } - return response; - } - - @Override - public void asyncProcessRequest(ChannelHandlerContext ctx, RemotingCommand request, RemotingResponseCallback responseCallback) throws Exception { - asyncProcessRequest(ctx, request).thenAcceptAsync(responseCallback::callback, this.brokerController.getPutMessageFutureExecutor()); - } - - public CompletableFuture asyncProcessRequest(ChannelHandlerContext ctx, - RemotingCommand request) throws RemotingCommandException { - final SendMessageContext mqtraceContext; + RemotingCommand request) throws RemotingCommandException { + SendMessageContext sendMessageContext; switch (request.getCode()) { case RequestCode.CONSUMER_SEND_MSG_BACK: - return this.asyncConsumerSendMsgBack(ctx, request); + return this.consumerSendMsgBack(ctx, request); default: SendMessageRequestHeader requestHeader = parseRequestHeader(request); if (requestHeader == null) { - return CompletableFuture.completedFuture(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; } - mqtraceContext = buildMsgContext(ctx, requestHeader); - this.executeSendMessageHookBefore(ctx, request, mqtraceContext); + + RemotingCommand response; if (requestHeader.isBatch()) { - return this.asyncSendBatchMessage(ctx, request, mqtraceContext, requestHeader); + response = this.sendBatchMessage(ctx, request, sendMessageContext, requestHeader, mappingContext, + (ctx1, response1) -> executeSendMessageHookAfter(response1, ctx1)); } else { - return this.asyncSendMessage(ctx, request, mqtraceContext, requestHeader); + response = this.sendMessage(ctx, request, sendMessageContext, requestHeader, mappingContext, + (ctx12, response12) -> executeSendMessageHookAfter(response12, ctx12)); } + + return response; } } @Override public boolean rejectRequest() { - return this.brokerController.getMessageStore().isOSPageCacheBusy() || - this.brokerController.getMessageStore().isTransientStorePoolDeficient(); - } - - private CompletableFuture asyncConsumerSendMsgBack(ChannelHandlerContext ctx, - RemotingCommand request) throws RemotingCommandException { - final RemotingCommand response = RemotingCommand.createResponseCommand(null); - final ConsumerSendMsgBackRequestHeader requestHeader = - (ConsumerSendMsgBackRequestHeader)request.decodeCommandCustomHeader(ConsumerSendMsgBackRequestHeader.class); - String namespace = NamespaceUtil.getNamespaceFromResource(requestHeader.getGroup()); - if (this.hasConsumeMessageHook() && !UtilAll.isBlank(requestHeader.getOriginMsgId())) { - ConsumeMessageContext context = buildConsumeMessageContext(namespace, requestHeader, request); - this.executeConsumeMessageHookAfter(context); - } - SubscriptionGroupConfig subscriptionGroupConfig = - this.brokerController.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 CompletableFuture.completedFuture(response); - } - if (!PermName.isWriteable(this.brokerController.getBrokerConfig().getBrokerPermission())) { - response.setCode(ResponseCode.NO_PERMISSION); - response.setRemark("the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1() + "] sending message is forbidden"); - return CompletableFuture.completedFuture(response); + if (!this.brokerController.getBrokerConfig().isEnableSlaveActingMaster() && this.brokerController.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) { + return true; } - if (subscriptionGroupConfig.getRetryQueueNums() <= 0) { - response.setCode(ResponseCode.SUCCESS); - response.setRemark(null); - return CompletableFuture.completedFuture(response); - } - - String newTopic = MixAll.getRetryTopic(requestHeader.getGroup()); - int queueIdInt = ThreadLocalRandom.current().nextInt(99999999) % subscriptionGroupConfig.getRetryQueueNums(); - int topicSysFlag = 0; - if (requestHeader.isUnitMode()) { - topicSysFlag = TopicSysFlag.buildSysFlag(false, true); - } - - TopicConfig topicConfig = this.brokerController.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 CompletableFuture.completedFuture(response); + if (this.brokerController.getMessageStore().isOSPageCacheBusy() || this.brokerController.getMessageStore().isTransientStorePoolDeficient()) { + return true; } - if (!PermName.isWriteable(topicConfig.getPerm())) { - response.setCode(ResponseCode.NO_PERMISSION); - response.setRemark(String.format("the topic[%s] sending message is forbidden", newTopic)); - return CompletableFuture.completedFuture(response); - } - MessageExt msgExt = this.brokerController.getMessageStore().lookMessageByOffset(requestHeader.getOffset()); - if (null == msgExt) { - response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark("look message by offset failed, " + requestHeader.getOffset()); - return CompletableFuture.completedFuture(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); + return false; + } - int delayLevel = requestHeader.getDelayLevel(); + /** + * If the response is not null, it meets some errors + * + * @return + */ - int maxReconsumeTimes = subscriptionGroupConfig.getRetryMaxTimes(); - if (request.getVersion() >= MQVersion.Version.V3_4_9.ordinal()) { - Integer times = requestHeader.getMaxReconsumeTimes(); - if (times != null) { - maxReconsumeTimes = times; + private RemotingCommand rewriteResponseForStaticTopic(SendMessageResponseHeader responseHeader, + TopicQueueMappingContext mappingContext) { + try { + if (mappingContext.getMappingDetail() == null) { + return null; } - } - - if (msgExt.getReconsumeTimes() >= maxReconsumeTimes - || delayLevel < 0) { - newTopic = MixAll.getDLQTopic(requestHeader.getGroup()); - queueIdInt = ThreadLocalRandom.current().nextInt(99999999) % DLQ_NUMS_PER_GROUP; + TopicQueueMappingDetail mappingDetail = mappingContext.getMappingDetail(); - topicConfig = this.brokerController.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 CompletableFuture.completedFuture(response); + 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())); } - msgExt.setDelayTimeLevel(0); - } else { - if (0 == delayLevel) { - delayLevel = 3 + msgExt.getReconsumeTimes(); + //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())); } - msgExt.setDelayTimeLevel(delayLevel); + responseHeader.setQueueId(mappingContext.getGlobalId()); + responseHeader.setQueueOffset(staticLogicOffset); + } catch (Throwable t) { + return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage()); } - - 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(msgExt.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())); - - CompletableFuture putMessageResult = this.brokerController.getMessageStore().asyncPutMessage(msgInner); - return putMessageResult.thenApply((r) -> { - if (r != null) { - switch (r.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())) { - this.brokerController.getBrokerStatsManager().incTopicPutNums(msgInner.getTopic()); - this.brokerController.getBrokerStatsManager().incTopicPutSize(msgInner.getTopic(), r.getAppendMessageResult().getWroteBytes()); - this.brokerController.getBrokerStatsManager().incQueuePutNums(msgInner.getTopic(), msgInner.getQueueId()); - this.brokerController.getBrokerStatsManager().incQueuePutSize(msgInner.getTopic(), msgInner.getQueueId(), r.getAppendMessageResult().getWroteBytes()); - } - this.brokerController.getBrokerStatsManager().incSendBackNums(requestHeader.getGroup(), backTopic); - response.setCode(ResponseCode.SUCCESS); - response.setRemark(null); - return response; - default: - break; - } - response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark(r.getPutMessageStatus().name()); - return response; - } - response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark("putMessageResult is null"); - return response; - }); - } - - - private CompletableFuture asyncSendMessage(ChannelHandlerContext ctx, RemotingCommand request, - SendMessageContext mqtraceContext, - SendMessageRequestHeader requestHeader) { - final RemotingCommand response = preSend(ctx, request, requestHeader); - final SendMessageResponseHeader responseHeader = (SendMessageResponseHeader)response.readCustomHeader(); - - if (response.getCode() != -1) { - return CompletableFuture.completedFuture(response); - } - - 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); - - if (!handleRetryAndDLQ(requestHeader, response, request, msgInner, topicConfig)) { - return CompletableFuture.completedFuture(response); - } - - msgInner.setBody(body); - msgInner.setFlag(requestHeader.getFlag()); - Map origProps = MessageDecoder.string2messageProperties(requestHeader.getProperties()); - MessageAccessor.setProperties(msgInner, origProps); - 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); - if (origProps.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 = origProps.remove(MessageConst.PROPERTY_WAIT_STORE_MSG_OK); - msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); - // Reput to properties, since msgInner.isWaitStoreMsgOK() will be invoked later - origProps.put(MessageConst.PROPERTY_WAIT_STORE_MSG_OK, waitStoreMsgOKValue); - } else { - msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); - } - - CompletableFuture putMessageResult = null; - String transFlag = origProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED); - if (transFlag != null && Boolean.parseBoolean(transFlag)) { - if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) { - response.setCode(ResponseCode.NO_PERMISSION); - response.setRemark( - "the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1() - + "] sending transaction message is forbidden"); - return CompletableFuture.completedFuture(response); - } - putMessageResult = this.brokerController.getTransactionalMessageService().asyncPrepareMessage(msgInner); - } else { - putMessageResult = this.brokerController.getMessageStore().asyncPutMessage(msgInner); - } - return handlePutMessageResultFuture(putMessageResult, response, request, msgInner, responseHeader, mqtraceContext, ctx, queueIdInt); - } - - private CompletableFuture handlePutMessageResultFuture(CompletableFuture putMessageResult, - RemotingCommand response, - RemotingCommand request, - MessageExt msgInner, - SendMessageResponseHeader responseHeader, - SendMessageContext sendMessageContext, - ChannelHandlerContext ctx, - int queueIdInt) { - return putMessageResult.thenApply((r) -> - handlePutMessageResult(r, response, request, msgInner, responseHeader, sendMessageContext, ctx, queueIdInt) - ); + return null; } private boolean handleRetryAndDLQ(SendMessageRequestHeader requestHeader, RemotingCommand response, - RemotingCommand request, - MessageExt msg, TopicConfig topicConfig) { + RemotingCommand request, + MessageExt msg, TopicConfig topicConfig, Map properties) { String newTopic = requestHeader.getTopic(); if (null != newTopic && newTopic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { String groupName = newTopic.substring(MixAll.RETRY_GROUP_TOPIC_PREFIX.length()); @@ -362,9 +184,26 @@ private boolean handleRetryAndDLQ(SendMessageRequestHeader requestHeader, Remoti maxReconsumeTimes = requestHeader.getMaxReconsumeTimes(); } int reconsumeTimes = requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes(); - if (reconsumeTimes >= maxReconsumeTimes) { + + 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 = BrokerMetricsManager.newAttributesBuilder() + .put(LABEL_CONSUMER_GROUP, requestHeader.getProducerGroup()) + .put(LABEL_TOPIC, requestHeader.getTopic()) + .put(LABEL_IS_SYSTEM, BrokerMetricsManager.isSystem(requestHeader.getTopic(), requestHeader.getProducerGroup())) + .build(); + BrokerMetricsManager.sendToDlqMessages.add(1, attributes); + + properties.put(MessageConst.PROPERTY_DELAY_TIME_LEVEL, "-1"); newTopic = MixAll.getDLQTopic(groupName); - int queueIdInt = ThreadLocalRandom.current().nextInt(99999999) % DLQ_NUMS_PER_GROUP; + int queueIdInt = randomQueueId(DLQ_NUMS_PER_GROUP); topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(newTopic, DLQ_NUMS_PER_GROUP, PermName.PERM_WRITE | PermName.PERM_READ, 0 @@ -387,65 +226,72 @@ private boolean handleRetryAndDLQ(SendMessageRequestHeader requestHeader, Remoti return true; } - private RemotingCommand sendMessage(final ChannelHandlerContext ctx, - final RemotingCommand request, - final SendMessageContext sendMessageContext, - final SendMessageRequestHeader requestHeader) throws RemotingCommandException { - - final RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); - final SendMessageResponseHeader responseHeader = (SendMessageResponseHeader)response.readCustomHeader(); - - response.setOpaque(request.getOpaque()); + public RemotingCommand sendMessage(final ChannelHandlerContext ctx, + final RemotingCommand request, + final SendMessageContext sendMessageContext, + final SendMessageRequestHeader requestHeader, + final TopicQueueMappingContext mappingContext, + final SendMessageCallback sendMessageCallback) throws RemotingCommandException { - 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 SendMessage request command, {}", request); - - final long startTimstamp = this.brokerController.getBrokerConfig().getStartAcceptSendRequestTimeStamp(); - if (this.brokerController.getMessageStore().now() < startTimstamp) { - response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark(String.format("broker unable to service, until %s", UtilAll.timeMillisToHumanString2(startTimstamp))); - return response; - } - - response.setCode(-1); - super.msgCheck(ctx, requestHeader, response); + 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 = ThreadLocalRandom.current().nextInt(99999999) % topicConfig.getWriteQueueNums(); + queueIdInt = randomQueueId(topicConfig.getWriteQueueNums()); } MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(requestHeader.getTopic()); msgInner.setQueueId(queueIdInt); - if (!handleRetryAndDLQ(requestHeader, response, request, msgInner, topicConfig)) { + Map oriProps = MessageDecoder.string2messageProperties(requestHeader.getProperties()); + if (!handleRetryAndDLQ(requestHeader, response, request, msgInner, topicConfig, oriProps)) { return response; } msgInner.setBody(body); msgInner.setFlag(requestHeader.getFlag()); - MessageAccessor.setProperties(msgInner, MessageDecoder.string2messageProperties(requestHeader.getProperties())); + + 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); + } + + MessageAccessor.setProperties(msgInner, oriProps); + + 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())); - PutMessageResult putMessageResult = null; - Map oriProps = MessageDecoder.string2messageProperties(requestHeader.getProperties()); + + // Map oriProps = MessageDecoder.string2messageProperties(requestHeader.getProperties()); String traFlag = oriProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED); - if (traFlag != null && Boolean.parseBoolean(traFlag) + boolean sendTransactionPrepareMessage = false; + 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); @@ -454,19 +300,49 @@ private RemotingCommand sendMessage(final ChannelHandlerContext ctx, + "] sending transaction message is forbidden"); return response; } - putMessageResult = this.brokerController.getTransactionalMessageService().prepareMessage(msgInner); - } else { - putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner); + sendTransactionPrepareMessage = true; } - return handlePutMessageResult(putMessageResult, response, request, msgInner, responseHeader, sendMessageContext, ctx, queueIdInt); + 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); + } + 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)); + 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) { + 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"); @@ -494,15 +370,34 @@ private RemotingCommand handlePutMessageResult(PutMessageResult putMessageResult break; // Failed - case CREATE_MAPEDFILE_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( - "the message is illegal, maybe msg body or properties length not matched. msg body length limit 128k, msg properties length limit 32k."); + 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); @@ -510,7 +405,7 @@ private RemotingCommand handlePutMessageResult(PutMessageResult putMessageResult "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_PAGECACHE_BUSY: + case OS_PAGE_CACHE_BUSY: response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("[PC_SYNCHRONIZED]broker busy, start flow control for a while"); break; @@ -529,6 +424,10 @@ private RemotingCommand handlePutMessageResult(PutMessageResult putMessageResult } 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())) { @@ -539,13 +438,32 @@ private RemotingCommand handlePutMessageResult(PutMessageResult putMessageResult this.brokerController.getBrokerStatsManager().incTopicPutNums(msg.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum(), 1); this.brokerController.getBrokerStatsManager().incTopicPutSize(msg.getTopic(), putMessageResult.getAppendMessageResult().getWroteBytes()); - this.brokerController.getBrokerStatsManager().incBrokerPutNums(putMessageResult.getAppendMessageResult().getMsgNum()); + 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 = BrokerMetricsManager.newAttributesBuilder() + .put(LABEL_TOPIC, msg.getTopic()) + .put(LABEL_MESSAGE_TYPE, messageType.getMetricsValue()) + .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(msg.getTopic())) + .build(); + BrokerMetricsManager.messagesInTotal.add(putMessageResult.getAppendMessageResult().getMsgNum(), attributes); + BrokerMetricsManager.throughputInTotal.add(putMessageResult.getAppendMessageResult().getWroteBytes(), attributes); + BrokerMetricsManager.messageSize.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)); + + RemotingCommand rewriteResult = rewriteResponseForStaticTopic(responseHeader, mappingContext); + if (rewriteResult != null) { + return rewriteResult; + } doResponse(ctx, request, response); @@ -556,36 +474,61 @@ private RemotingCommand handlePutMessageResult(PutMessageResult putMessageResult int commercialBaseCount = brokerController.getBrokerConfig().getCommercialBaseCount(); int wroteSize = putMessageResult.getAppendMessageResult().getWroteBytes(); - int incValue = (int)Math.ceil(wroteSize / BrokerStatsManager.SIZE_PER_COUNT) * commercialBaseCount; + 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 incValue = (int)Math.ceil(wroteSize / BrokerStatsManager.SIZE_PER_COUNT); + 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(incValue); + 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 CompletableFuture asyncSendBatchMessage(ChannelHandlerContext ctx, RemotingCommand request, - SendMessageContext mqtraceContext, - SendMessageRequestHeader requestHeader) { + 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(); + final SendMessageResponseHeader responseHeader = (SendMessageResponseHeader) response.readCustomHeader(); if (response.getCode() != -1) { - return CompletableFuture.completedFuture(response); + return response; } int queueIdInt = requestHeader.getQueueId(); @@ -598,9 +541,14 @@ private CompletableFuture asyncSendBatchMessage(ChannelHandlerC if (requestHeader.getTopic().length() > Byte.MAX_VALUE) { response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark("message topic length too long " + requestHeader.getTopic().length()); - return CompletableFuture.completedFuture(response); + 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); @@ -621,33 +569,60 @@ private CompletableFuture asyncSendBatchMessage(ChannelHandlerC String clusterName = this.brokerController.getBrokerConfig().getBrokerClusterName(); MessageAccessor.putProperty(messageExtBatch, MessageConst.PROPERTY_CLUSTER, clusterName); - CompletableFuture putMessageResult = this.brokerController.getMessageStore().asyncPutMessages(messageExtBatch); - return handlePutMessageResultFuture(putMessageResult, response, request, messageExtBatch, responseHeader, mqtraceContext, ctx, queueIdInt); - } + 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())); - public boolean hasConsumeMessageHook() { - return consumeMessageHookList != null && !this.consumeMessageHookList.isEmpty(); - } + MessageAccessor.putProperty(messageExtBatch, MessageConst.PROPERTY_INNER_NUM, String.valueOf(innerNum)); + messageExtBatch.setPropertiesString(MessageDecoder.messageProperties2String(messageExtBatch.getProperties())); - public void executeConsumeMessageHookAfter(final ConsumeMessageContext context) { - if (hasConsumeMessageHook()) { - for (ConsumeMessageHook hook : this.consumeMessageHookList) { - try { - hook.consumeMessageAfter(context); - } catch (Throwable e) { - // Ignore + // 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; } } - @Override - public SocketAddress getStoreHost() { - return storeHost; - } - private String diskUtil() { double physicRatio = 100; String storePath; @@ -657,7 +632,7 @@ private String diskUtil() { } else { storePath = this.brokerController.getMessageStoreConfig().getStorePathCommitLog(); } - String[] paths = storePath.trim().split(MessageStoreConfig.MULTI_PATH_SPLITTER); + String[] paths = storePath.trim().split(MixAll.MULTI_PATH_SPLITTER); for (String storePathPhysic : paths) { physicRatio = Math.min(physicRatio, UtilAll.getDiskPartitionSpaceUsedPercent(storePathPhysic)); } @@ -673,29 +648,8 @@ private String diskUtil() { return String.format("CL: %5.2f CQ: %5.2f INDEX: %5.2f", physicRatio, logisRatio, indexRatio); } - public void registerConsumeMessageHook(List consumeMessageHookList) { - this.consumeMessageHookList = consumeMessageHookList; - } - - static private ConsumeMessageContext buildConsumeMessageContext(String namespace, - ConsumerSendMsgBackRequestHeader requestHeader, - RemotingCommand request) { - ConsumeMessageContext context = new ConsumeMessageContext(); - context.setNamespace(namespace); - context.setConsumerGroup(requestHeader.getGroup()); - context.setTopic(requestHeader.getOriginTopic()); - context.setCommercialRcvStats(BrokerStatsManager.StatsType.SEND_BACK); - context.setCommercialRcvTimes(1); - context.setCommercialOwner(request.getExtFields().get(BrokerStatsManager.COMMERCIAL_OWNER)); - return context; - } - - private int randomQueueId(int writeQueueNums) { - return ThreadLocalRandom.current().nextInt(99999999) % writeQueueNums; - } - private RemotingCommand preSend(ChannelHandlerContext ctx, RemotingCommand request, - SendMessageRequestHeader requestHeader) { + SendMessageRequestHeader requestHeader) { final RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); response.setOpaque(request.getOpaque()); @@ -703,7 +657,7 @@ private RemotingCommand preSend(ChannelHandlerContext ctx, RemotingCommand reque 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 SendMessage request command {}", request); + LOGGER.debug("Receive SendMessage request command {}", request); final long startTimestamp = this.brokerController.getBrokerConfig().getStartAcceptSendRequestTimeStamp(); @@ -714,11 +668,9 @@ private RemotingCommand preSend(ChannelHandlerContext ctx, RemotingCommand reque } response.setCode(-1); - super.msgCheck(ctx, requestHeader, response); - if (response.getCode() != -1) { - return response; - } + super.msgCheck(ctx, requestHeader, request, response); return response; } + } diff --git a/store/src/main/java/org/apache/rocketmq/store/schedule/DelayOffsetSerializeWrapper.java b/broker/src/main/java/org/apache/rocketmq/broker/schedule/DelayOffsetSerializeWrapper.java similarity index 78% rename from store/src/main/java/org/apache/rocketmq/store/schedule/DelayOffsetSerializeWrapper.java rename to broker/src/main/java/org/apache/rocketmq/broker/schedule/DelayOffsetSerializeWrapper.java index 7021992c598..2485c9a9bcf 100644 --- a/store/src/main/java/org/apache/rocketmq/store/schedule/DelayOffsetSerializeWrapper.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/schedule/DelayOffsetSerializeWrapper.java @@ -14,15 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.store.schedule; +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); + new ConcurrentHashMap<>(32); + + private DataVersion dataVersion; public ConcurrentMap getOffsetTable() { return offsetTable; @@ -31,4 +34,12 @@ public ConcurrentMap getOffsetTable() { public void setOffsetTable(ConcurrentMap offsetTable) { this.offsetTable = offsetTable; } + + public DataVersion getDataVersion() { + return dataVersion; + } + + public void setDataVersion(DataVersion dataVersion) { + this.dataVersion = dataVersion; + } } diff --git a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java b/broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java similarity index 71% rename from store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java rename to broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java index d5b4e8d3909..2a4ace09850 100644 --- a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java @@ -14,10 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.store.schedule; +package org.apache.rocketmq.broker.schedule; +import io.opentelemetry.api.common.Attributes; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import java.util.Queue; import java.util.concurrent.CompletableFuture; @@ -29,31 +29,41 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; 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.broker.metrics.BrokerMetricsManager; 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.topic.TopicValidator; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; 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.store.ConsumeQueue; -import org.apache.rocketmq.store.ConsumeQueueExt; -import org.apache.rocketmq.store.DefaultMessageStore; -import org.apache.rocketmq.store.MessageExtBrokerInner; -import org.apache.rocketmq.store.MessageStore; +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.SelectMappedBufferResult; import org.apache.rocketmq.store.config.StorePathConfigHelper; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + 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; @@ -62,26 +72,27 @@ public class ScheduleMessageService extends ConfigManager { private static final long DELAY_FOR_A_SLEEP = 10L; private final ConcurrentMap delayLevelTable = - new ConcurrentHashMap(32); + new ConcurrentHashMap<>(32); private final ConcurrentMap offsetTable = - new ConcurrentHashMap(32); - private final DefaultMessageStore defaultMessageStore; + new ConcurrentHashMap<>(32); private final AtomicBoolean started = new AtomicBoolean(false); private ScheduledExecutorService deliverExecutorService; - private MessageStore writeMessageStore; 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); - - public ScheduleMessageService(final DefaultMessageStore defaultMessageStore) { - this.defaultMessageStore = defaultMessageStore; - this.writeMessageStore = defaultMessageStore; - if (defaultMessageStore != null) { - this.enableAsyncDeliver = defaultMessageStore.getMessageStoreConfig().isEnableScheduleAsyncDeliver(); - } + 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 = new ScheduledThreadPoolExecutor(1, + new ThreadFactoryImpl("ScheduleMessageServicePersistThread", true, brokerController.getBrokerConfig())); } public static int queueId2DelayLevel(final int queueId) { @@ -92,20 +103,11 @@ public static int delayLevel2QueueId(final int delayLevel) { return delayLevel - 1; } - /** - * @param writeMessageStore the writeMessageStore to set - */ - public void setWriteMessageStore(MessageStore writeMessageStore) { - this.writeMessageStore = writeMessageStore; - } - public void buildRunningStats(HashMap stats) { - Iterator> it = this.offsetTable.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry next = it.next(); + for (Map.Entry next : this.offsetTable.entrySet()) { int queueId = delayLevel2QueueId(next.getKey()); long delayOffset = next.getValue(); - long maxOffset = this.defaultMessageStore.getMaxOffsetInQueue(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, queueId); + 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); @@ -114,6 +116,10 @@ public void buildRunningStats(HashMap stats) { 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) { @@ -127,7 +133,7 @@ public long computeDeliverTimestamp(final int delayLevel, final long storeTimest public void start() { if (started.compareAndSet(false, true)) { - super.load(); + this.load(); this.deliverExecutorService = new ScheduledThreadPoolExecutor(this.maxDelayLevel, new ThreadFactoryImpl("ScheduleMessageTimerThread_")); if (this.enableAsyncDeliver) { this.handleExecutorService = new ScheduledThreadPoolExecutor(this.maxDelayLevel, new ThreadFactoryImpl("ScheduleMessageExecutorHandleThread_")); @@ -148,23 +154,22 @@ public void start() { } } - this.deliverExecutorService.scheduleAtFixedRate(new Runnable() { - - @Override - public void run() { - try { - if (started.get()) { - ScheduleMessageService.this.persist(); - } - } catch (Throwable e) { - log.error("scheduleAtFixedRate flush exception", e); - } + scheduledPersistService.scheduleAtFixedRate(() -> { + try { + ScheduleMessageService.this.persist(); + } catch (Throwable e) { + log.error("scheduleAtFixedRate flush exception", e); } - }, 10000, this.defaultMessageStore.getMessageStoreConfig().getFlushDelayOffsetInterval(), TimeUnit.MILLISECONDS); + }, 10000, this.brokerController.getMessageStoreConfig().getFlushDelayOffsetInterval(), TimeUnit.MILLISECONDS); } } public void shutdown() { + stop(); + ThreadUtils.shutdown(scheduledPersistService); + } + + public void stop() { if (this.started.compareAndSet(true, false) && null != this.deliverExecutorService) { this.deliverExecutorService.shutdown(); try { @@ -182,10 +187,8 @@ public void shutdown() { } } - if (this.deliverPendingTable != null) { - for (int i = 1; i <= this.deliverPendingTable.size(); i++) { - log.warn("deliverPendingTable level: {}, size: {}", i, this.deliverPendingTable.get(i).size()); - } + for (int i = 1; i <= this.deliverPendingTable.size(); i++) { + log.warn("deliverPendingTable level: {}, size: {}", i, this.deliverPendingTable.get(i).size()); } this.persist(); @@ -200,6 +203,14 @@ 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); @@ -216,8 +227,8 @@ public boolean load() { public boolean correctDelayOffset() { try { for (int delayLevel : delayLevelTable.keySet()) { - ConsumeQueue cq = - ScheduleMessageService.this.defaultMessageStore.findConsumeQueue(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, + ConsumeQueueInterface cq = + brokerController.getMessageStore().getQueueStore().findOrCreateConsumeQueue(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, delayLevel2QueueId(delayLevel)); Long currentDelayOffset = offsetTable.get(delayLevel); if (currentDelayOffset == null || cq == null) { @@ -251,7 +262,7 @@ public boolean correctDelayOffset() { @Override public String configFilePath() { - return StorePathConfigHelper.getDelayOffsetStorePath(this.defaultMessageStore.getMessageStoreConfig() + return StorePathConfigHelper.getDelayOffsetStorePath(this.brokerController.getMessageStore().getMessageStoreConfig() .getStorePathRootDir()); } @@ -262,6 +273,10 @@ public void decode(String jsonString) { DelayOffsetSerializeWrapper.fromJson(jsonString, DelayOffsetSerializeWrapper.class); if (delayOffsetSerializeWrapper != null) { this.offsetTable.putAll(delayOffsetSerializeWrapper.getOffsetTable()); + // For compatible + if (delayOffsetSerializeWrapper.getDataVersion() != null) { + this.dataVersion.assignNewOne(delayOffsetSerializeWrapper.getDataVersion()); + } } } } @@ -270,17 +285,18 @@ public void decode(String jsonString) { 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(); + 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.defaultMessageStore.getMessageStoreConfig().getMessageDelayLevel(); + String levelString = this.brokerController.getMessageStoreConfig().getMessageDelayLevel(); try { String[] levelArray = levelString.split(" "); for (int i = 0; i < levelArray.length; i++) { @@ -300,15 +316,14 @@ public boolean parseDelayLevel() { } } } catch (Exception e) { - log.error("parseDelayLevel exception", e); - log.info("levelString String = {}", levelString); + log.error("parse message delay level failed. messageDelayLevel = {}", levelString, e); return false; } return true; } - private MessageExtBrokerInner messageTimeup(MessageExt msgExt) { + private MessageExtBrokerInner messageTimeUp(MessageExt msgExt) { MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setBody(msgExt.getBody()); msgInner.setFlag(msgExt.getFlag()); @@ -328,6 +343,8 @@ private MessageExtBrokerInner messageTimeup(MessageExt msgExt) { 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)); @@ -351,18 +368,15 @@ public DeliverDelayedMessageTimerTask(int delayLevel, long offset) { public void run() { try { if (isStarted()) { - this.executeOnTimeup(); + this.executeOnTimeUp(); } } catch (Exception e) { // XXX: warn and notify me - log.error("ScheduleMessageService, executeOnTimeup exception", e); + log.error("ScheduleMessageService, executeOnTimeUp exception", e); this.scheduleNextTimerTask(this.offset, DELAY_FOR_A_PERIOD); } } - /** - * @return - */ private long correctDeliverTimestamp(final long now, final long deliverTimestamp) { long result = deliverTimestamp; @@ -375,9 +389,9 @@ private long correctDeliverTimestamp(final long now, final long deliverTimestamp return result; } - public void executeOnTimeup() { - ConsumeQueue cq = - ScheduleMessageService.this.defaultMessageStore.findConsumeQueue(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, + public void executeOnTimeUp() { + ConsumeQueueInterface cq = + ScheduleMessageService.this.brokerController.getMessageStore().getConsumeQueue(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, delayLevel2QueueId(delayLevel)); if (cq == null) { @@ -385,7 +399,7 @@ public void executeOnTimeup() { return; } - SelectMappedBufferResult bufferCQ = cq.getIndexBuffer(this.offset); + ReferredIterator bufferCQ = cq.iterateFrom(this.offset); if (bufferCQ == null) { long resetOffset; if ((resetOffset = cq.getMinOffsetInQueue()) > this.offset) { @@ -404,41 +418,40 @@ public void executeOnTimeup() { long nextOffset = this.offset; try { - int i = 0; - ConsumeQueueExt.CqExtUnit cqExtUnit = new ConsumeQueueExt.CqExtUnit(); - for (; i < bufferCQ.getSize() && isStarted(); i += ConsumeQueue.CQ_STORE_UNIT_SIZE) { - long offsetPy = bufferCQ.getByteBuffer().getLong(); - int sizePy = bufferCQ.getByteBuffer().getInt(); - long tagsCode = bufferCQ.getByteBuffer().getLong(); - - if (cq.isExtAddr(tagsCode)) { - if (cq.getExt(tagsCode, cqExtUnit)) { - tagsCode = cqExtUnit.getTagsCode(); - } else { - //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 = defaultMessageStore.getCommitLog().pickupStoreTimestamp(offsetPy, sizePy); - tagsCode = computeDeliverTimestamp(delayLevel, msgStoreTime); - } + 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); - nextOffset = offset + (i / ConsumeQueue.CQ_STORE_UNIT_SIZE); + + long currOffset = cqUnit.getQueueOffset(); + assert cqUnit.getBatchNum() == 1; + nextOffset = currOffset + cqUnit.getBatchNum(); long countdown = deliverTimestamp - now; if (countdown > 0) { - this.scheduleNextTimerTask(nextOffset, DELAY_FOR_A_WHILE); + this.scheduleNextTimerTask(currOffset, DELAY_FOR_A_WHILE); + ScheduleMessageService.this.updateOffset(this.delayLevel, currOffset); return; } - MessageExt msgExt = ScheduleMessageService.this.defaultMessageStore.lookMessageByOffset(offsetPy, sizePy); + MessageExt msgExt = ScheduleMessageService.this.brokerController.getMessageStore().lookMessageByOffset(offsetPy, sizePy); if (msgExt == null) { continue; } - MessageExtBrokerInner msgInner = ScheduleMessageService.this.messageTimeup(msgExt); + 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); @@ -447,9 +460,9 @@ public void executeOnTimeup() { boolean deliverSuc; if (ScheduleMessageService.this.enableAsyncDeliver) { - deliverSuc = this.asyncDeliver(msgInner, msgExt.getMsgId(), offset, offsetPy, sizePy); + deliverSuc = this.asyncDeliver(msgInner, msgExt.getMsgId(), currOffset, offsetPy, sizePy); } else { - deliverSuc = this.syncDeliver(msgInner, msgExt.getMsgId(), offset, offsetPy, sizePy); + deliverSuc = this.syncDeliver(msgInner, msgExt.getMsgId(), currOffset, offsetPy, sizePy); } if (!deliverSuc) { @@ -457,10 +470,8 @@ public void executeOnTimeup() { return; } } - - nextOffset = this.offset + (i / ConsumeQueue.CQ_STORE_UNIT_SIZE); } catch (Exception e) { - log.error("ScheduleMessageService, messageTimeup execute error, offset = {}", nextOffset, e); + log.error("ScheduleMessageService, messageTimeUp execute error, offset = {}", nextOffset, e); } finally { bufferCQ.release(); } @@ -490,7 +501,7 @@ private boolean asyncDeliver(MessageExtBrokerInner msgInner, String msgId, long //Flow Control int currentPendingNum = processesQueue.size(); - int maxPendingLimit = ScheduleMessageService.this.defaultMessageStore.getMessageStoreConfig() + int maxPendingLimit = brokerController.getMessageStoreConfig() .getScheduleAsyncDeliverMaxPendingLimit(); if (currentPendingNum > maxPendingLimit) { log.warn("Asynchronous deliver triggers flow control, " + @@ -513,7 +524,7 @@ private boolean asyncDeliver(MessageExtBrokerInner msgInner, String msgId, long private PutResultProcess deliverMessage(MessageExtBrokerInner msgInner, String msgId, long offset, long offsetPy, int sizePy, boolean autoResend) { CompletableFuture future = - ScheduleMessageService.this.writeMessageStore.asyncPutMessage(msgInner); + brokerController.getEscapeBridge().asyncPutMessage(msgInner); return new PutResultProcess() .setTopic(msgInner.getTopic()) .setDelayLevel(this.delayLevel) @@ -555,7 +566,7 @@ public void run() { return; } log.warn("putResultProcess error, info={}", putResultProcess.toString()); - putResultProcess.onException(); + putResultProcess.doResend(); break; case SKIP: log.warn("putResultProcess skip, info={}", putResultProcess.toString()); @@ -564,7 +575,7 @@ public void run() { } } catch (Exception e) { log.error("HandlePutResultTask exception. info={}", putResultProcess.toString(), e); - putResultProcess.onException(); + putResultProcess.doResend(); } } @@ -585,7 +596,7 @@ public class PutResultProcess { private boolean autoResend = false; private CompletableFuture future; - private volatile int resendCount = 0; + private volatile AtomicInteger resendCount = new AtomicInteger(0); private volatile ProcessStatus status = ProcessStatus.RUNNING; public PutResultProcess setTopic(String topic) { @@ -664,14 +675,12 @@ public CompletableFuture getFuture() { return future; } - public int getResendCount() { + public AtomicInteger getResendCount() { return resendCount; } public PutResultProcess thenProcess() { - this.future.thenAccept(result -> { - this.handleResult(result); - }); + this.future.thenAccept(this::handleResult); this.future.exceptionally(e -> { log.error("ScheduleMessageService put message exceptionally, info: {}", @@ -694,21 +703,39 @@ private void handleResult(PutMessageResult result) { public void onSuccess(PutMessageResult result) { this.status = ProcessStatus.SUCCESS; - if (ScheduleMessageService.this.defaultMessageStore.getMessageStoreConfig().isEnableScheduleMessageStats()) { - ScheduleMessageService.this.defaultMessageStore.getBrokerStatsManager().incQueueGetNums(MixAll.SCHEDULE_CONSUMER_GROUP, TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, delayLevel - 1, result.getAppendMessageResult().getMsgNum()); - ScheduleMessageService.this.defaultMessageStore.getBrokerStatsManager().incQueueGetSize(MixAll.SCHEDULE_CONSUMER_GROUP, TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, delayLevel - 1, result.getAppendMessageResult().getWroteBytes()); - ScheduleMessageService.this.defaultMessageStore.getBrokerStatsManager().incGroupGetNums(MixAll.SCHEDULE_CONSUMER_GROUP, TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, result.getAppendMessageResult().getMsgNum()); - ScheduleMessageService.this.defaultMessageStore.getBrokerStatsManager().incGroupGetSize(MixAll.SCHEDULE_CONSUMER_GROUP, TopicValidator.RMQ_SYS_SCHEDULE_TOPIC, result.getAppendMessageResult().getWroteBytes()); - ScheduleMessageService.this.defaultMessageStore.getBrokerStatsManager().incTopicPutNums(this.topic, result.getAppendMessageResult().getMsgNum(), 1); - ScheduleMessageService.this.defaultMessageStore.getBrokerStatsManager().incTopicPutSize(this.topic, result.getAppendMessageResult().getWroteBytes()); - ScheduleMessageService.this.defaultMessageStore.getBrokerStatsManager().incBrokerPutNums(result.getAppendMessageResult().getMsgNum()); + 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 = BrokerMetricsManager.newAttributesBuilder() + .put(LABEL_TOPIC, TopicValidator.RMQ_SYS_SCHEDULE_TOPIC) + .put(LABEL_CONSUMER_GROUP, MixAll.SCHEDULE_CONSUMER_GROUP) + .put(LABEL_IS_SYSTEM, true) + .build(); + BrokerMetricsManager.messagesOutTotal.add(result.getAppendMessageResult().getMsgNum(), attributes); + BrokerMetricsManager.throughputOutTotal.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 = BrokerMetricsManager.newAttributesBuilder() + .put(LABEL_TOPIC, topic) + .put(LABEL_MESSAGE_TYPE, TopicMessageType.DELAY.getMetricsValue()) + .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(topic)) + .build(); + BrokerMetricsManager.messagesInTotal.add(result.getAppendMessageResult().getMsgNum(), attributes); + BrokerMetricsManager.throughputInTotal.add(result.getAppendMessageResult().getWroteBytes(), attributes); + BrokerMetricsManager.messageSize.record(result.getAppendMessageResult().getWroteBytes() / result.getAppendMessageResult().getMsgNum(), attributes); } } public void onException() { log.warn("ScheduleMessageService onException, info: {}", this.toString()); if (this.autoResend) { - this.resend(); + this.status = ProcessStatus.EXCEPTION; } else { this.status = ProcessStatus.SKIP; } @@ -726,26 +753,26 @@ public PutMessageResult get() { } } - private void resend() { + public void doResend() { log.info("Resend message, info: {}", this.toString()); // Gradually increase the resend interval. try { - Thread.sleep(Math.min(this.resendCount++ * 100, 60 * 1000)); + Thread.sleep(Math.min(this.resendCount.incrementAndGet() * 100, 60 * 1000)); } catch (InterruptedException e) { e.printStackTrace(); } try { - MessageExt msgExt = ScheduleMessageService.this.defaultMessageStore.lookMessageByOffset(this.physicOffset, this.physicSize); + 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.writeMessageStore.putMessage(msgInner); + 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()); @@ -757,15 +784,15 @@ private void resend() { } public boolean need2Blocked() { - int maxResendNum2Blocked = ScheduleMessageService.this.defaultMessageStore.getMessageStoreConfig() + int maxResendNum2Blocked = ScheduleMessageService.this.brokerController.getMessageStore().getMessageStoreConfig() .getScheduleAsyncDeliverMaxResendNum2Blocked(); - return this.resendCount > maxResendNum2Blocked; + return this.resendCount.get() > maxResendNum2Blocked; } public boolean need2Skip() { - int maxResendNum2Blocked = ScheduleMessageService.this.defaultMessageStore.getMessageStoreConfig() + int maxResendNum2Blocked = ScheduleMessageService.this.brokerController.getMessageStore().getMessageStoreConfig() .getScheduleAsyncDeliverMaxResendNum2Blocked(); - return this.resendCount > maxResendNum2Blocked * 2; + return this.resendCount.get() > maxResendNum2Blocked * 2; } @Override @@ -787,24 +814,26 @@ public String toString() { public 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. - * */ + * 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 put message. When the message cannot be looked, the message will be skipped. + */ SKIP, } + + public ConcurrentMap getOffsetTable() { + return offsetTable; + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java b/broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java index 7b5e5645a0b..b9de5173be9 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java @@ -17,19 +17,29 @@ package org.apache.rocketmq.broker.slave; import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +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.common.MixAll; +import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.protocol.body.ConsumerOffsetSerializeWrapper; -import org.apache.rocketmq.common.protocol.body.SubscriptionGroupWrapper; -import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper; +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.SubscriptionGroupWrapper; +import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper; 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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; private volatile String masterAddr = null; @@ -42,7 +52,10 @@ public String getMasterAddr() { } public void setMasterAddr(String masterAddr) { - this.masterAddr = masterAddr; + if (!StringUtils.equals(this.masterAddr, masterAddr)) { + LOGGER.info("Update master address from {} to {}", this.masterAddr, masterAddr); + this.masterAddr = masterAddr; + } } public void syncAll() { @@ -50,28 +63,61 @@ public void syncAll() { 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 { - TopicConfigSerializeWrapper topicWrapper = - this.brokerController.getBrokerOuterAPI().getAllTopicConfig(masterAddrBak); + TopicConfigAndMappingSerializeWrapper topicWrapper = + this.brokerController.getBrokerOuterAPI().getAllTopicConfig(masterAddrBak); if (!this.brokerController.getTopicConfigManager().getDataVersion() - .equals(topicWrapper.getDataVersion())) { + .equals(topicWrapper.getDataVersion())) { this.brokerController.getTopicConfigManager().getDataVersion() - .assignNewOne(topicWrapper.getDataVersion()); - this.brokerController.getTopicConfigManager().getTopicConfigTable().clear(); - this.brokerController.getTopicConfigManager().getTopicConfigTable() - .putAll(topicWrapper.getTopicConfigTable()); + .assignNewOne(topicWrapper.getDataVersion()); + + ConcurrentMap newTopicConfigTable = topicWrapper.getTopicConfigTable(); + //delete + ConcurrentMap topicConfigTable = this.brokerController.getTopicConfigManager().getTopicConfigTable(); + for (Iterator> it = topicConfigTable.entrySet().iterator(); it.hasNext(); ) { + Map.Entry item = it.next(); + if (!newTopicConfigTable.containsKey(item.getKey())) { + it.remove(); + } + } + //update + topicConfigTable.putAll(newTopicConfigTable); + this.brokerController.getTopicConfigManager().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(); + for (Iterator> it = topicConfigTable.entrySet().iterator(); it.hasNext(); ) { + Map.Entry item = it.next(); + if (!newTopicConfigTable.containsKey(item.getKey())) { + it.remove(); + } + } + //update + topicConfigTable.putAll(newTopicConfigTable); - log.info("Update slave topic config from master, {}", masterAddrBak); + this.brokerController.getTopicQueueMappingManager().persist(); } + LOGGER.info("Update slave topic config from master, {}", masterAddrBak); } catch (Exception e) { - log.error("SyncTopicConfig Exception, {}", masterAddrBak, e); + LOGGER.error("SyncTopicConfig Exception, {}", masterAddrBak, e); } } } @@ -81,13 +127,14 @@ private void syncConsumerOffset() { if (masterAddrBak != null && !masterAddrBak.equals(brokerController.getBrokerAddr())) { try { ConsumerOffsetSerializeWrapper offsetWrapper = - this.brokerController.getBrokerOuterAPI().getAllConsumerOffset(masterAddrBak); + this.brokerController.getBrokerOuterAPI().getAllConsumerOffset(masterAddrBak); this.brokerController.getConsumerOffsetManager().getOffsetTable() - .putAll(offsetWrapper.getOffsetTable()); + .putAll(offsetWrapper.getOffsetTable()); + this.brokerController.getConsumerOffsetManager().getDataVersion().assignNewOne(offsetWrapper.getDataVersion()); this.brokerController.getConsumerOffsetManager().persist(); - log.info("Update slave consumer offset from master, {}", masterAddrBak); + LOGGER.info("Update slave consumer offset from master, {}", masterAddrBak); } catch (Exception e) { - log.error("SyncConsumerOffset Exception, {}", masterAddrBak, e); + LOGGER.error("SyncConsumerOffset Exception, {}", masterAddrBak, e); } } } @@ -97,47 +144,106 @@ private void syncDelayOffset() { if (masterAddrBak != null && !masterAddrBak.equals(brokerController.getBrokerAddr())) { try { String delayOffset = - this.brokerController.getBrokerOuterAPI().getAllDelayOffset(masterAddrBak); + this.brokerController.getBrokerOuterAPI().getAllDelayOffset(masterAddrBak); if (delayOffset != null) { String fileName = - StorePathConfigHelper.getDelayOffsetStorePath(this.brokerController - .getMessageStoreConfig().getStorePathRootDir()); + StorePathConfigHelper.getDelayOffsetStorePath(this.brokerController + .getMessageStoreConfig().getStorePathRootDir()); try { MixAll.string2File(delayOffset, fileName); + this.brokerController.getScheduleMessageService().load(); } catch (IOException e) { - log.error("Persist file Exception, {}", fileName, e); + LOGGER.error("Persist file Exception, {}", fileName, e); } } - log.info("Update slave delay offset from master, {}", masterAddrBak); + LOGGER.info("Update slave delay offset from master, {}", masterAddrBak); } catch (Exception e) { - log.error("SyncDelayOffset Exception, {}", masterAddrBak, e); + LOGGER.error("SyncDelayOffset Exception, {}", masterAddrBak, e); } } } private void syncSubscriptionGroupConfig() { String masterAddrBak = this.masterAddr; - if (masterAddrBak != null && !masterAddrBak.equals(brokerController.getBrokerAddr())) { + if (masterAddrBak != null && !masterAddrBak.equals(brokerController.getBrokerAddr())) { try { SubscriptionGroupWrapper subscriptionWrapper = - this.brokerController.getBrokerOuterAPI() - .getAllSubscriptionGroupConfig(masterAddrBak); + this.brokerController.getBrokerOuterAPI() + .getAllSubscriptionGroupConfig(masterAddrBak); if (!this.brokerController.getSubscriptionGroupManager().getDataVersion() - .equals(subscriptionWrapper.getDataVersion())) { + .equals(subscriptionWrapper.getDataVersion())) { SubscriptionGroupManager subscriptionGroupManager = - this.brokerController.getSubscriptionGroupManager(); + this.brokerController.getSubscriptionGroupManager(); subscriptionGroupManager.getDataVersion().assignNewOne( - subscriptionWrapper.getDataVersion()); + subscriptionWrapper.getDataVersion()); subscriptionGroupManager.getSubscriptionGroupTable().clear(); subscriptionGroupManager.getSubscriptionGroupTable().putAll( - subscriptionWrapper.getSubscriptionGroupTable()); + subscriptionWrapper.getSubscriptionGroupTable()); subscriptionGroupManager.persist(); - log.info("Update slave Subscription Group from master, {}", masterAddrBak); + 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(); + messageRequestModeManager.getMessageRequestModeMap().clear(); + messageRequestModeManager.getMessageRequestModeMap().putAll( + messageRequestModeSerializeWrapper.getMessageRequestModeMap() + ); + 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()) { + TimerCheckpoint checkpoint = this.brokerController.getBrokerOuterAPI().getTimerCheckPoint(masterAddrBak); + if (null != this.brokerController.getTimerCheckpoint()) { + this.brokerController.getTimerCheckpoint().setLastReadTimeMs(checkpoint.getLastReadTimeMs()); + this.brokerController.getTimerCheckpoint().setMasterTimerQueueOffset(checkpoint.getMasterTimerQueueOffset()); + } + } + } 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) { - log.error("SyncSubscriptionGroup Exception, {}", masterAddrBak, e); + LOGGER.error("SyncTimerMetrics Exception, {}", masterAddrBak, e); } } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/subscription/LmqSubscriptionGroupManager.java b/broker/src/main/java/org/apache/rocketmq/broker/subscription/LmqSubscriptionGroupManager.java index 635b935b823..018083811e8 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/subscription/LmqSubscriptionGroupManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/subscription/LmqSubscriptionGroupManager.java @@ -18,7 +18,7 @@ import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.MixAll; -import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig; +import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; public class LmqSubscriptionGroupManager extends SubscriptionGroupManager { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManager.java b/broker/src/main/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManager.java index 41f7a8a4714..0ae11313f20 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManager.java @@ -16,26 +16,38 @@ */ package org.apache.rocketmq.broker.subscription; +import com.google.common.collect.ImmutableMap; +import java.util.HashMap; 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.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; +import org.apache.rocketmq.client.Validators; import org.apache.rocketmq.common.ConfigManager; -import org.apache.rocketmq.common.DataVersion; 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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig; +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; public class SubscriptionGroupManager extends ConfigManager { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + + private ConcurrentMap subscriptionGroupTable = + new ConcurrentHashMap<>(1024); + + private ConcurrentMap> forbiddenTable = + new ConcurrentHashMap<>(4); - private final ConcurrentMap subscriptionGroupTable = - new ConcurrentHashMap(1024); private final DataVersion dataVersion = new DataVersion(); private transient BrokerController brokerController; @@ -94,9 +106,27 @@ private void init() { subscriptionGroupConfig.setConsumeBroadcastEnable(true); this.subscriptionGroupTable.put(MixAll.CID_ONSAPI_OWNER_GROUP, subscriptionGroupConfig); } + + { + SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); + subscriptionGroupConfig.setGroupName(MixAll.CID_SYS_RMQ_TRANS); + subscriptionGroupConfig.setConsumeBroadcastEnable(true); + this.subscriptionGroupTable.put(MixAll.CID_SYS_RMQ_TRANS, subscriptionGroupConfig); + } } public void updateSubscriptionGroupConfig(final 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 = this.subscriptionGroupTable.put(config.getGroupName(), config); if (old != null) { log.info("update subscription group config, old: {} new: {}", old, config); @@ -104,7 +134,85 @@ public void updateSubscriptionGroupConfig(final SubscriptionGroupConfig config) log.info("create new subscription group, {}", config); } - this.dataVersion.nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); + + 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) + * + * @param group + * @param topic + * @param forbiddenIndex 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) + * + * @param group + * @param topic + * @param forbiddenIndex 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; + } + + private 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); + } + + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); this.persist(); } @@ -113,7 +221,8 @@ public void disableConsume(final String groupName) { SubscriptionGroupConfig old = this.subscriptionGroupTable.get(groupName); if (old != null) { old.setConsumeEnable(false); - this.dataVersion.nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); } } @@ -121,13 +230,17 @@ public SubscriptionGroupConfig findSubscriptionGroupConfig(final String group) { SubscriptionGroupConfig subscriptionGroupConfig = this.subscriptionGroupTable.get(group); if (null == subscriptionGroupConfig) { if (brokerController.getBrokerConfig().isAutoCreateSubscriptionGroup() || MixAll.isSysConsumerGroup(group)) { + if (group.length() > Validators.CHARACTER_MAX_LENGTH || TopicValidator.isTopicOrGroupIllegal(group)) { + return null; + } subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setGroupName(group); SubscriptionGroupConfig preConfig = this.subscriptionGroupTable.putIfAbsent(group, subscriptionGroupConfig); if (null == preConfig) { log.info("auto create a subscription group, {}", subscriptionGroupConfig.toString()); } - this.dataVersion.nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); this.persist(); } } @@ -152,12 +265,16 @@ public void decode(String jsonString) { 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); } @@ -174,18 +291,59 @@ public ConcurrentMap getSubscriptionGroupTable( return subscriptionGroupTable; } + public ConcurrentMap> getForbiddenTable() { + return forbiddenTable; + } + + public void setForbiddenTable( + ConcurrentMap> forbiddenTable) { + this.forbiddenTable = forbiddenTable; + } + public DataVersion getDataVersion() { return dataVersion; } public void deleteSubscriptionGroupConfig(final String groupName) { SubscriptionGroupConfig old = this.subscriptionGroupTable.remove(groupName); + this.forbiddenTable.remove(groupName); if (old != null) { log.info("delete subscription group OK, subscription group:{}", old); - this.dataVersion.nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); 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; + } + } + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/LmqTopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/LmqTopicConfigManager.java index d021758b2fd..ca5a94a901b 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/topic/LmqTopicConfigManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/LmqTopicConfigManager.java @@ -46,4 +46,12 @@ 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); + } + } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java index e09080afaea..e5fdd8675fa 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java @@ -16,6 +16,8 @@ */ package org.apache.rocketmq.broker.topic; +import com.google.common.collect.ImmutableMap; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; @@ -25,31 +27,36 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +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.DataVersion; 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.protocol.body.KVTable; -import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper; import org.apache.rocketmq.common.sysflag.TopicSysFlag; import org.apache.rocketmq.common.topic.TopicValidator; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.TopicConfigSerializeWrapper; + +import static com.google.common.base.Preconditions.checkNotNull; public class TopicConfigManager extends ConfigManager { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + private 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(); - - private final ConcurrentMap topicConfigTable = - new ConcurrentHashMap(1024); - private final DataVersion dataVersion = new DataVersion(); + private ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(1024); + private DataVersion dataVersion = new DataVersion(); private transient BrokerController brokerController; public TopicConfigManager() { @@ -88,7 +95,6 @@ public TopicConfigManager(BrokerController brokerController) { this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig); } { - String topic = this.brokerController.getBrokerConfig().getBrokerClusterName(); TopicConfig topicConfig = new TopicConfig(topic); TopicValidator.addSystemTopic(topic); @@ -147,6 +153,44 @@ public TopicConfigManager(BrokerController brokerController) { topicConfig.setWriteQueueNums(1); this.topicConfigTable.put(topicConfig.getTopicName(), 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()); + this.topicConfigTable.put(topicConfig.getTopicName(), 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); + this.topicConfigTable.put(topicConfig.getTopicName(), 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); + this.topicConfigTable.put(topicConfig.getTopicName(), 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); + this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig); + } } public TopicConfig selectTopicConfig(final String topic) { @@ -162,8 +206,9 @@ public TopicConfig createTopicInSendMessageMethod(final String topic, final Stri if (this.topicConfigTableLock.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { try { topicConfig = this.topicConfigTable.get(topic); - if (topicConfig != null) + if (topicConfig != null) { return topicConfig; + } TopicConfig defaultTopicConfig = this.topicConfigTable.get(defaultTopic); if (defaultTopicConfig != null) { @@ -204,7 +249,8 @@ public TopicConfig createTopicInSendMessageMethod(final String topic, final Stri this.topicConfigTable.put(topic, topicConfig); - this.dataVersion.nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); createNew = true; @@ -225,14 +271,67 @@ public TopicConfig createTopicInSendMessageMethod(final String topic, final Stri 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 = this.topicConfigTable.get(topicConfig.getTopicName()); + if (existedTopicConfig != null) { + return existedTopicConfig; + } + log.info("Create new topic [{}] config:[{}]", topicConfig.getTopicName(), topicConfig); + this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); + createNew = true; + this.persist(); + } finally { + this.topicConfigTableLock.unlock(); + } + } + } catch (InterruptedException e) { + log.error("createTopicIfAbsent ", e); + } + if (createNew && register) { + this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion); + } + return this.topicConfigTable.get(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 = this.topicConfigTable.get(topic); - if (topicConfig != null) + if (topicConfig != null) { + if (isOrder != topicConfig.isOrder()) { + topicConfig.setOrder(isOrder); + this.updateTopicConfig(topicConfig); + } return topicConfig; + } boolean createNew = false; @@ -240,19 +339,22 @@ public TopicConfig createTopicInSendMessageBackMethod( if (this.topicConfigTableLock.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { try { topicConfig = this.topicConfigTable.get(topic); - if (topicConfig != null) + 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); this.topicConfigTable.put(topic, topicConfig); createNew = true; - this.dataVersion.nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); this.persist(); } finally { this.topicConfigTableLock.unlock(); @@ -292,7 +394,8 @@ public TopicConfig createTopicOfTranCheckMaxTime(final int clientDefaultTopicQue log.info("create new topic {}", topicConfig); this.topicConfigTable.put(TopicValidator.RMQ_SYS_TRANS_CHECK_MAX_TIME_TOPIC, topicConfig); createNew = true; - this.dataVersion.nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); this.persist(); } finally { this.topicConfigTableLock.unlock(); @@ -325,7 +428,8 @@ public void updateTopicUnitFlag(final String topic, final boolean unit) { this.topicConfigTable.put(topic, topicConfig); - this.dataVersion.nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); this.persist(); this.brokerController.registerBrokerAll(false, true, true); @@ -347,7 +451,8 @@ public void updateTopicUnitSubFlag(final String topic, final boolean hasUnitSub) this.topicConfigTable.put(topic, topicConfig); - this.dataVersion.nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); this.persist(); this.brokerController.registerBrokerAll(false, true, true); @@ -355,6 +460,19 @@ public void updateTopicUnitSubFlag(final String topic, final boolean hasUnitSub) } public void updateTopicConfig(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); + TopicConfig old = this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig); if (old != null) { log.info("update topic config, old:[{}] new:[{}]", old, topicConfig); @@ -362,9 +480,10 @@ public void updateTopicConfig(final TopicConfig topicConfig) { log.info("create new topic [{}]", topicConfig); } - this.dataVersion.nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); - this.persist(); + this.persist(topicConfig.getTopicName(), topicConfig); } public void updateOrderTopicConfig(final KVTable orderKVTableFromNs) { @@ -381,7 +500,11 @@ public void updateOrderTopicConfig(final KVTable orderKVTableFromNs) { } } - for (Map.Entry entry : this.topicConfigTable.entrySet()) { + // We don't have a mandatory rule to maintain the validity of order conf in NameServer, + // so we may overwrite the order field mistakenly. + // To avoid the above case, we comment the below codes, please use mqadmin API to update + // the order filed. + /*for (Map.Entry entry : this.topicConfigTable.entrySet()) { String topic = entry.getKey(); if (!orderTopics.contains(topic)) { TopicConfig topicConfig = entry.getValue(); @@ -391,15 +514,21 @@ public void updateOrderTopicConfig(final KVTable orderKVTableFromNs) { log.info("update order topic config, topic={}, order={}", topic, false); } } - } + }*/ if (isChange) { - this.dataVersion.nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); this.persist(); } } } + // make it testable + public Map allAttributes() { + return TopicAttributes.ALL; + } + public boolean isOrderTopic(final String topic) { TopicConfig topicConfig = this.topicConfigTable.get(topic); if (topicConfig == null) { @@ -413,7 +542,8 @@ public void deleteTopicConfig(final String topic) { TopicConfig old = this.topicConfigTable.remove(topic); if (old != null) { log.info("delete topic config OK, topic: {}", old); - this.dataVersion.nextVersion(); + long stateMachineVersion = brokerController.getMessageStore() != null ? brokerController.getMessageStore().getStateMachineVersion() : 0; + dataVersion.nextVersion(stateMachineVersion); this.persist(); } else { log.warn("delete topic config failed, topic: {} not exists", topic); @@ -423,7 +553,9 @@ public void deleteTopicConfig(final String topic) { public TopicConfigSerializeWrapper buildTopicConfigSerializeWrapper() { TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setTopicConfigTable(this.topicConfigTable); - topicConfigSerializeWrapper.setDataVersion(this.dataVersion); + DataVersion dataVersionCopy = new DataVersion(); + dataVersionCopy.assignNewOne(this.dataVersion); + topicConfigSerializeWrapper.setDataVersion(dataVersionCopy); return topicConfigSerializeWrapper; } @@ -434,8 +566,7 @@ public String encode() { @Override public String configFilePath() { - return BrokerPathConfigHelper.getTopicConfigPath(this.brokerController.getMessageStoreConfig() - .getStorePathRootDir()); + return BrokerPathConfigHelper.getTopicConfigPath(this.brokerController.getMessageStoreConfig().getStorePathRootDir()); } @Override @@ -470,7 +601,36 @@ public DataVersion getDataVersion() { return dataVersion; } + public void setTopicConfigTable( + ConcurrentMap topicConfigTable) { + this.topicConfigTable = topicConfigTable; + } + public ConcurrentMap getTopicConfigTable() { return topicConfigTable; } + + private Map request(TopicConfig topicConfig) { + return topicConfig.getAttributes() == null ? new HashMap<>() : topicConfig.getAttributes(); + } + + private Map current(String topic) { + TopicConfig topicConfig = this.topicConfigTable.get(topic); + if (topicConfig == null) { + return new HashMap<>(); + } else { + Map attributes = topicConfig.getAttributes(); + if (attributes == null) { + return new HashMap<>(); + } else { + return attributes; + } + } + } + + public boolean containsTopic(String topic) { + return topicConfigTable.containsKey(topic); + } + + } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicQueueMappingCleanService.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicQueueMappingCleanService.java new file mode 100644 index 00000000000..7047ef8b47a --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicQueueMappingCleanService.java @@ -0,0 +1,336 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.setBname(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.setBname(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); + } + } + + + + +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicQueueMappingManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicQueueMappingManager.java new file mode 100644 index 00000000000..6b9cf159383 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicQueueMappingManager.java @@ -0,0 +1,258 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.fastjson.JSON; +import java.util.List; +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 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); + return JSON.toJSONString(wrapper, pretty); + } + + @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 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()); + } + } + +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicRouteInfoManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicRouteInfoManager.java new file mode 100644 index 00000000000..b3556472555 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicRouteInfoManager.java @@ -0,0 +1,258 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Executors; +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.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 = Executors.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; + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java index 0079fb5ce44..771d8430060 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java @@ -17,37 +17,29 @@ package org.apache.rocketmq.broker.transaction; import io.netty.channel.Channel; -import org.apache.rocketmq.broker.BrokerController; -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.protocol.header.CheckTransactionStateRequestHeader; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; - import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; 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.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 InternalLogger LOGGER = InternalLoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); + 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 static 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("Transaction-msg-check-thread"); - return thread; - } - }, new CallerRunsPolicy()); + private static volatile ExecutorService executorService; public AbstractTransactionalMessageCheckListener() { } @@ -63,6 +55,7 @@ public void sendCheckMessage(MessageExt msgExt) throws Exception { checkTransactionStateRequestHeader.setMsgId(msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX)); checkTransactionStateRequestHeader.setTransactionId(checkTransactionStateRequestHeader.getMsgId()); checkTransactionStateRequestHeader.setTranStateTableOffset(msgExt.getQueueOffset()); + checkTransactionStateRequestHeader.setBname(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); @@ -76,16 +69,20 @@ public void sendCheckMessage(MessageExt msgExt) throws Exception { } public void resolveHalfMsg(final MessageExt msgExt) { - executorService.execute(new Runnable() { - @Override - public void run() { - try { - sendCheckMessage(msgExt); - } catch (Exception e) { - LOGGER.error("Send check message error!", e); + 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() { @@ -93,7 +90,16 @@ public BrokerController getBrokerController() { } public void shutDown() { - executorService.shutdown(); + if (executorService != null) { + executorService.shutdown(); + } + } + + public synchronized void initExecutorService() { + if (executorService == null) { + executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000), + new ThreadFactoryImpl("Transaction-msg-check-thread", brokerController.getBrokerIdentity()), new CallerRunsPolicy()); + } } /** @@ -103,6 +109,7 @@ public void shutDown() { */ public void setBrokerController(BrokerController brokerController) { this.brokerController = brokerController; + initExecutorService(); } /** diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/OperationResult.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/OperationResult.java index 6c35f6adeb5..d23b7617e15 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/OperationResult.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/OperationResult.java @@ -25,10 +25,6 @@ public class OperationResult { private String responseRemark; - public MessageExt getPrepareMessage() { - return prepareMessage; - } - public void setPrepareMessage(MessageExt prepareMessage) { this.prepareMessage = prepareMessage; } @@ -48,4 +44,8 @@ public String getResponseRemark() { public void setResponseRemark(String responseRemark) { this.responseRemark = responseRemark; } + + public MessageExt getPrepareMessage() { + return prepareMessage; + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageCheckService.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageCheckService.java index a2cc0baa9e7..52209c3fbdb 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageCheckService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageCheckService.java @@ -19,11 +19,11 @@ import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class TransactionalMessageCheckService extends ServiceThread { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); private BrokerController brokerController; @@ -33,14 +33,17 @@ public TransactionalMessageCheckService(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!"); - long checkInterval = brokerController.getBrokerConfig().getTransactionCheckInterval(); while (!this.isStopped()) { + long checkInterval = brokerController.getBrokerConfig().getTransactionCheckInterval(); this.waitForRunning(checkInterval); } log.info("End transaction check service thread!"); diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageService.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageService.java index c8eefd357b1..8dbbf980ebe 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageService.java @@ -16,11 +16,11 @@ */ package org.apache.rocketmq.broker.transaction; +import java.util.concurrent.CompletableFuture; import org.apache.rocketmq.common.message.MessageExt; -import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader; -import org.apache.rocketmq.store.MessageExtBrokerInner; +import org.apache.rocketmq.common.message.MessageExtBrokerInner; +import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.store.PutMessageResult; -import java.util.concurrent.CompletableFuture; public interface TransactionalMessageService { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/jdbc/JDBCTransactionStore.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/jdbc/JDBCTransactionStore.java deleted file mode 100644 index da4958defaf..00000000000 --- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/jdbc/JDBCTransactionStore.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.jdbc; - -import java.net.URL; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.List; -import java.util.Properties; -import java.util.concurrent.atomic.AtomicLong; -import org.apache.rocketmq.broker.transaction.TransactionRecord; -import org.apache.rocketmq.broker.transaction.TransactionStore; -import org.apache.rocketmq.common.MixAll; -import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; - -public class JDBCTransactionStore implements TransactionStore { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); - private final JDBCTransactionStoreConfig jdbcTransactionStoreConfig; - private Connection connection; - private AtomicLong totalRecordsValue = new AtomicLong(0); - - public JDBCTransactionStore(JDBCTransactionStoreConfig jdbcTransactionStoreConfig) { - this.jdbcTransactionStoreConfig = jdbcTransactionStoreConfig; - } - - @Override - public boolean open() { - if (this.loadDriver()) { - Properties props = new Properties(); - props.put("user", jdbcTransactionStoreConfig.getJdbcUser()); - props.put("password", jdbcTransactionStoreConfig.getJdbcPassword()); - - try { - this.connection = - DriverManager.getConnection(this.jdbcTransactionStoreConfig.getJdbcURL(), props); - - this.connection.setAutoCommit(false); - - if (!this.computeTotalRecords()) { - return this.createDB(); - } - - return true; - } catch (SQLException e) { - log.info("Create JDBC Connection Exception", e); - } - } - - return false; - } - - private boolean loadDriver() { - try { - Class.forName(this.jdbcTransactionStoreConfig.getJdbcDriverClass()).newInstance(); - log.info("Loaded the appropriate driver, {}", - this.jdbcTransactionStoreConfig.getJdbcDriverClass()); - return true; - } catch (Exception e) { - log.info("Loaded the appropriate driver Exception", e); - } - - return false; - } - - private boolean computeTotalRecords() { - Statement statement = null; - ResultSet resultSet = null; - try { - statement = this.connection.createStatement(); - - resultSet = statement.executeQuery("select count(offset) as total from t_transaction"); - if (!resultSet.next()) { - log.warn("computeTotalRecords ResultSet is empty"); - return false; - } - - this.totalRecordsValue.set(resultSet.getLong(1)); - } catch (Exception e) { - log.warn("computeTotalRecords Exception", e); - return false; - } finally { - if (null != statement) { - try { - statement.close(); - } catch (SQLException e) { - } - } - - if (null != resultSet) { - try { - resultSet.close(); - } catch (SQLException e) { - } - } - } - - return true; - } - - private boolean createDB() { - Statement statement = null; - try { - statement = this.connection.createStatement(); - - String sql = this.createTableSql(); - log.info("createDB SQL:\n {}", sql); - statement.execute(sql); - this.connection.commit(); - return true; - } catch (Exception e) { - log.warn("createDB Exception", e); - return false; - } finally { - if (null != statement) { - try { - statement.close(); - } catch (SQLException e) { - log.warn("Close statement exception", e); - } - } - } - } - - private String createTableSql() { - URL resource = JDBCTransactionStore.class.getClassLoader().getResource("transaction.sql"); - String fileContent = MixAll.file2String(resource); - return fileContent; - } - - @Override - public void close() { - try { - if (this.connection != null) { - this.connection.close(); - } - } catch (SQLException e) { - } - } - - @Override - public boolean put(List trs) { - PreparedStatement statement = null; - try { - this.connection.setAutoCommit(false); - statement = this.connection.prepareStatement("insert into t_transaction values (?, ?)"); - for (TransactionRecord tr : trs) { - statement.setLong(1, tr.getOffset()); - statement.setString(2, tr.getProducerGroup()); - statement.addBatch(); - } - int[] executeBatch = statement.executeBatch(); - this.connection.commit(); - this.totalRecordsValue.addAndGet(updatedRows(executeBatch)); - return true; - } catch (Exception e) { - log.warn("createDB Exception", e); - return false; - } finally { - if (null != statement) { - try { - statement.close(); - } catch (SQLException e) { - log.warn("Close statement exception", e); - } - } - } - } - - private long updatedRows(int[] rows) { - long res = 0; - for (int i : rows) { - res += i; - } - - return res; - } - - @Override - public void remove(List pks) { - PreparedStatement statement = null; - try { - this.connection.setAutoCommit(false); - statement = this.connection.prepareStatement("DELETE FROM t_transaction WHERE offset = ?"); - for (long pk : pks) { - statement.setLong(1, pk); - statement.addBatch(); - } - int[] executeBatch = statement.executeBatch(); - this.connection.commit(); - } catch (Exception e) { - log.warn("createDB Exception", e); - } finally { - if (null != statement) { - try { - statement.close(); - } catch (SQLException e) { - } - } - } - } - - @Override - public List traverse(long pk, int nums) { - return null; - } - - @Override - public long totalRecords() { - return this.totalRecordsValue.get(); - } - - @Override - public long minPK() { - return 0; - } - - @Override - public long maxPK() { - return 0; - } -} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/jdbc/JDBCTransactionStoreConfig.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/jdbc/JDBCTransactionStoreConfig.java deleted file mode 100644 index 4b079597c4d..00000000000 --- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/jdbc/JDBCTransactionStoreConfig.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.jdbc; - -public class JDBCTransactionStoreConfig { - private String jdbcDriverClass = "com.mysql.jdbc.Driver"; - private String jdbcURL = "jdbc:mysql://xxx.xxx.xxx.xxx:1000/xxx?useUnicode=true&characterEncoding=UTF-8"; - private String jdbcUser = "xxx"; - private String jdbcPassword = "xxx"; - - public String getJdbcDriverClass() { - return jdbcDriverClass; - } - - public void setJdbcDriverClass(String jdbcDriverClass) { - this.jdbcDriverClass = jdbcDriverClass; - } - - public String getJdbcURL() { - return jdbcURL; - } - - public void setJdbcURL(String jdbcURL) { - this.jdbcURL = jdbcURL; - } - - public String getJdbcUser() { - return jdbcUser; - } - - public void setJdbcUser(String jdbcUser) { - this.jdbcUser = jdbcUser; - } - - public String getJdbcPassword() { - return jdbcPassword; - } - - public void setJdbcPassword(String jdbcPassword) { - this.jdbcPassword = jdbcPassword; - } -} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListener.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListener.java index a28e3324950..ad02ae4270b 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListener.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListener.java @@ -24,16 +24,16 @@ 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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.store.MessageExtBrokerInner; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import java.util.concurrent.ThreadLocalRandom; public class DefaultTransactionalMessageCheckListener extends AbstractTransactionalMessageCheckListener { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); public DefaultTransactionalMessageCheckListener() { super(); diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/MessageQueueOpContext.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/MessageQueueOpContext.java new file mode 100644 index 00000000000..e8e5f13de6b --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/MessageQueueOpContext.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageBridge.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageBridge.java index 9d0152d55bb..2383f4f917c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageBridge.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageBridge.java @@ -16,9 +16,21 @@ */ 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.broker.metrics.BrokerMetricsManager; 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; @@ -28,32 +40,27 @@ 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.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.common.sysflag.MessageSysFlag; -import org.apache.rocketmq.logging.InnerLoggerFactory; -import org.apache.rocketmq.logging.InternalLogger; +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.MessageExtBrokerInner; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; -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 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 InternalLogger LOGGER = InnerLoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); - private final ConcurrentHashMap opQueueMap = new ConcurrentHashMap<>(); + private final ConcurrentHashMap opQueueMap = new ConcurrentHashMap<>(); private final BrokerController brokerController; private final MessageStore store; private final SocketAddress storeHost; @@ -131,13 +138,22 @@ private PullResult getMessage(String group, String topic, int queueId, long offs getMessageResult.getMessageCount()); this.brokerController.getBrokerStatsManager().incGroupGetSize(group, topic, getMessageResult.getBufferTotalSize()); - this.brokerController.getBrokerStatsManager().incBrokerGetNums(getMessageResult.getMessageCount()); + 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 = BrokerMetricsManager.newAttributesBuilder() + .put(LABEL_TOPIC, topic) + .put(LABEL_CONSUMER_GROUP, group) + .put(LABEL_IS_SYSTEM, TopicValidator.isSystemTopic(topic) || MixAll.isSysConsumerGroup(group)) + .build(); + BrokerMetricsManager.messagesOutTotal.add(getMessageResult.getMessageCount(), attributes); + BrokerMetricsManager.throughputOutTotal.add(getMessageResult.getBufferTotalSize(), attributes); + break; case NO_MATCHED_MESSAGE: pullStatus = PullStatus.NO_MATCHED_MSG; @@ -201,6 +217,10 @@ public CompletableFuture asyncPutHalfMessage(MessageExtBrokerI } 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())); @@ -212,18 +232,16 @@ private MessageExtBrokerInner parseHalfMessageInner(MessageExtBrokerInner msgInn return msgInner; } - public boolean putOpMessage(MessageExt messageExt, String opType) { - MessageQueue messageQueue = new MessageQueue(messageExt.getTopic(), - this.brokerController.getBrokerConfig().getBrokerName(), messageExt.getQueueId()); - if (TransactionalMessageUtil.REMOVETAG.equals(opType)) { - return addRemoveTagInTransactionOp(messageExt, messageQueue); - } - return true; - } - public PutMessageResult putMessageReturnResult(MessageExtBrokerInner messageInner) { LOGGER.debug("[BUG-TO-FIX] Thread:{} msgID:{}", Thread.currentThread().getName(), messageInner.getMsgId()); - return store.putMessage(messageInner); + 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) { @@ -243,7 +261,7 @@ public MessageExtBrokerInner renewImmunityHalfMessageInner(MessageExt msgExt) { String queueOffsetFromPrepare = msgExt.getUserProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET); if (null != queueOffsetFromPrepare) { MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET, - String.valueOf(queueOffsetFromPrepare)); + queueOffsetFromPrepare); } else { MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET, String.valueOf(msgExt.getQueueOffset())); @@ -299,43 +317,29 @@ private TopicConfig selectTopicConfig(String topic) { return topicConfig; } - /** - * Use this function while transaction msg is committed or rollback write a flag 'd' to operation queue for the - * msg's offset - * - * @param prepareMessage Half message - * @param messageQueue Half message queue - * @return This method will always return true. - */ - private boolean addRemoveTagInTransactionOp(MessageExt prepareMessage, MessageQueue messageQueue) { - Message message = new Message(TransactionalMessageUtil.buildOpTopic(), TransactionalMessageUtil.REMOVETAG, - String.valueOf(prepareMessage.getQueueOffset()).getBytes(TransactionalMessageUtil.charset)); - writeOp(message, messageQueue); - return true; - } - - private void writeOp(Message message, MessageQueue mq) { - MessageQueue opQueue; - if (opQueueMap.containsKey(mq)) { - opQueue = opQueueMap.get(mq); - } else { - opQueue = getOpQueueByHalf(mq); - MessageQueue oldQueue = opQueueMap.putIfAbsent(mq, opQueue); + 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; } } - if (opQueue == null) { - opQueue = new MessageQueue(TransactionalMessageUtil.buildOpTopic(), mq.getBrokerName(), mq.getQueueId()); + + PutMessageResult result = putMessageReturnResult(makeOpMessageInner(message, opQueue)); + if (result != null && result.getPutMessageStatus() == PutMessageStatus.PUT_OK) { + return true; } - putMessage(makeOpMessageInner(message, opQueue)); + + return false; } - private MessageQueue getOpQueueByHalf(MessageQueue halfMQ) { + private MessageQueue getOpQueueByHalf(Integer queueId, String brokerName) { MessageQueue opQueue = new MessageQueue(); opQueue.setTopic(TransactionalMessageUtil.buildOpTopic()); - opQueue.setBrokerName(halfMQ.getBrokerName()); - opQueue.setQueueId(halfMQ.getQueueId()); + opQueue.setBrokerName(brokerName); + opQueue.setQueueId(queueId); return opQueue; } @@ -346,4 +350,15 @@ public MessageExt lookMessageByOffset(final long 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; + } + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImpl.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImpl.java index 3f244f4a240..93fa725a93c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImpl.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImpl.java @@ -16,49 +16,66 @@ */ 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.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.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.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader; import org.apache.rocketmq.common.topic.TopicValidator; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.store.MessageExtBrokerInner; +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 java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; +import org.apache.rocketmq.store.config.BrokerRole; public class TransactionalMessageServiceImpl implements TransactionalMessageService { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); + 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<>(); + public TransactionalMessageServiceImpl(TransactionalMessageBridge transactionBridge) { this.transactionalMessageBridge = transactionBridge; + transactionalOpBatchService = new TransactionalOpBatchService(transactionalMessageBridge.getBrokerController(), this); + transactionalOpBatchService.start(); } - private ConcurrentHashMap opQueueMap = new ConcurrentHashMap<>(); @Override public CompletableFuture asyncPrepareMessage(MessageExtBrokerInner messageInner) { @@ -148,7 +165,8 @@ public void check(long transactionTimeout, int transactionCheckMax, List doneOpOffset = new ArrayList<>(); HashMap removeMap = new HashMap<>(); - PullResult pullResult = fillOpRemoveMap(removeMap, opQueue, opOffset, halfOffset, doneOpOffset); + 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); @@ -158,6 +176,10 @@ public void check(long transactionTimeout, int transactionCheckMax, 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); @@ -166,7 +188,11 @@ public void check(long transactionTimeout, int transactionCheckMax, if (removeMap.containsKey(i)) { log.debug("Half offset {} has been committed/rolled back", i); Long removedOpOffset = removeMap.remove(i); - doneOpOffset.add(removedOpOffset); + 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(); @@ -187,6 +213,35 @@ public void check(long transactionTimeout, int transactionCheckMax, } } + 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; @@ -205,33 +260,52 @@ public void check(long transactionTimeout, int transactionCheckMax, if (null != checkImmunityTimeStr) { checkImmunityTime = getImmunityTime(checkImmunityTimeStr, transactionTimeout); if (valueOfCurrentMinusBorn < checkImmunityTime) { - if (checkPrepareQueueOffset(removeMap, doneOpOffset, msgExt)) { + if (checkPrepareQueueOffset(removeMap, doneOpOffset, msgExt, checkImmunityTimeStr)) { newOffset = i + 1; i++; continue; } } } else { - if ((0 <= valueOfCurrentMinusBorn) && (valueOfCurrentMinusBorn < checkImmunityTime)) { + 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.getMsgFoundList(); - boolean isNeedCheck = (opMsg == null && valueOfCurrentMinusBorn > checkImmunityTime) - || (opMsg != null && (opMsg.get(opMsg.size() - 1).getBornTimestamp() - startTime > transactionTimeout)) - || (valueOfCurrentMinusBorn <= -1); + 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 { - pullResult = fillOpRemoveMap(removeMap, opQueue, pullResult.getNextBeginOffset(), halfOffset, doneOpOffset); - log.debug("The miss offset:{} in messageQueue:{} need to get more opMsg, result is:{}", i, - messageQueue, pullResult); + 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; } } @@ -245,6 +319,15 @@ public void check(long transactionTimeout, int transactionCheckMax, 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); @@ -271,12 +354,13 @@ private long getImmunityTime(String checkImmunityTimeStr, long transactionTimeou * @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, List doneOpOffset) { - PullResult pullResult = pullOpMsg(opQueue, pullOffsetOfOp, 32); + 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; } @@ -297,21 +381,42 @@ private PullResult fillOpRemoveMap(HashMap removeMap, return pullResult; } for (MessageExt opMessageExt : opMsg) { - Long queueOffset = getLong(new String(opMessageExt.getBody(), TransactionalMessageUtil.charset)); + 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(), queueOffset); - if (TransactionalMessageUtil.REMOVETAG.equals(opMessageExt.getTags())) { - if (queueOffset < miniOffset) { - doneOpOffset.add(opMessageExt.getQueueOffset()); - } else { - removeMap.put(queueOffset, opMessageExt.getQueueOffset()); + 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; } @@ -324,7 +429,7 @@ private PullResult fillOpRemoveMap(HashMap removeMap, * @return Return true if put success, otherwise return false. */ private boolean checkPrepareQueueOffset(HashMap removeMap, List doneOpOffset, - MessageExt msgExt) { + MessageExt msgExt, String checkImmunityTimeStr) { String prepareQueueOffsetStr = msgExt.getUserProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET); if (null == prepareQueueOffsetStr) { return putImmunityMsgBackToHalfQueue(msgExt); @@ -336,6 +441,11 @@ private boolean checkPrepareQueueOffset(HashMap removeMap, List messageExts = result.getMsgFoundList(); - if (messageExts == null) { - return getResult; + if (result != null) { + getResult.setPullResult(result); + List messageExts = result.getMsgFoundList(); + if (messageExts == null || messageExts.size() == 0) { + return getResult; + } + getResult.setMsg(messageExts.get(0)); } - getResult.setMsg(messageExts.get(0)); return getResult; } @@ -464,12 +576,38 @@ private OperationResult getHalfMessageByOffset(long commitLogOffset) { } @Override - public boolean deletePrepareMessage(MessageExt msgExt) { - if (this.transactionalMessageBridge.putOpMessage(msgExt, TransactionalMessageUtil.REMOVETAG)) { - log.debug("Transaction op message write successfully. messageId={}, queueId={} msgExt:{}", msgExt.getMsgId(), msgExt.getQueueId(), msgExt); + 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 {}", msgExt.getMsgId(), msgExt.getQueueId()); + log.error("Transaction op message write failed. messageId is {}, queueId is {}", messageExt.getMsgId(), messageExt.getQueueId()); return false; } } @@ -494,4 +632,105 @@ public void close() { } + 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; + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageUtil.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageUtil.java index e6baf0266cc..555ae4d2940 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageUtil.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageUtil.java @@ -16,14 +16,23 @@ */ 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; -import java.nio.charset.Charset; - public class TransactionalMessageUtil { - public static final String REMOVETAG = "d"; - public static Charset charset = Charset.forName("utf-8"); + 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; @@ -37,4 +46,48 @@ 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; + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalOpBatchService.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalOpBatchService.java new file mode 100644 index 00000000000..fb6e9e8ce1a --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalOpBatchService.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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(); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/util/HookUtils.java b/broker/src/main/java/org/apache/rocketmq/broker/util/HookUtils.java new file mode 100644 index 00000000000..dec42351d9f --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/util/HookUtils.java @@ -0,0 +1,253 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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; + } + + 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; + } +} diff --git a/broker/src/main/resources/META-INF/service/org.apache.rocketmq.acl.AccessValidator b/broker/src/main/resources/META-INF/service/org.apache.rocketmq.acl.AccessValidator deleted file mode 100644 index 1abc92e0162..00000000000 --- a/broker/src/main/resources/META-INF/service/org.apache.rocketmq.acl.AccessValidator +++ /dev/null @@ -1 +0,0 @@ -org.apache.rocketmq.acl.plain.PlainAccessValidator \ No newline at end of file diff --git a/broker/src/main/resources/rmq.broker.logback.xml b/broker/src/main/resources/rmq.broker.logback.xml new file mode 100644 index 00000000000..78b1aea4110 --- /dev/null +++ b/broker/src/main/resources/rmq.broker.logback.xml @@ -0,0 +1,642 @@ + + + + + + + + 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}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}/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_metric.log + + true + + + ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}broker_metric.%i.log.gz + + 1 + 10 + + + 500MB + + + %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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java index 9706334187e..75ad961ce9f 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java @@ -18,6 +18,7 @@ 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; @@ -30,37 +31,50 @@ import org.apache.rocketmq.remoting.netty.RequestTask; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; -import org.junit.Ignore; +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( - new BrokerConfig(), - new NettyServerConfig(), - new NettyClientConfig(), - new MessageStoreConfig()); - assertThat(brokerController.initialize()); + BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig); + assertThat(brokerController.initialize()).isTrue(); brokerController.start(); brokerController.shutdown(); } @After public void destroy() { - UtilAll.deleteFile(new File(new MessageStoreConfig().getStorePathRootDir())); + UtilAll.deleteFile(new File(messageStoreConfig.getStorePathRootDir())); } @Test public void testHeadSlowTimeMills() throws Exception { - BrokerController brokerController = new BrokerController( - new BrokerConfig(), - new NettyServerConfig(), - new NettyClientConfig(), - new MessageStoreConfig()); + BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig); brokerController.initialize(); BlockingQueue queue = new LinkedBlockingQueue<>(); diff --git a/broker/src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest.java b/broker/src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest.java index 9ea1eeee31b..2541e755e39 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest.java @@ -22,23 +22,37 @@ import com.google.common.collect.Lists; import io.netty.channel.ChannelHandlerContext; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.common.BrokerConfig; -import org.apache.rocketmq.common.namesrv.RegisterBrokerResult; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper; -import org.apache.rocketmq.common.protocol.header.namesrv.QueryDataVersionResponseHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.RegisterBrokerResponseHeader; +import org.apache.rocketmq.common.BrokerIdentity; +import org.apache.rocketmq.common.MixAll; +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.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.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.invocation.InvocationOnMock; @@ -46,10 +60,14 @@ import org.mockito.stubbing.Answer; 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.when; @RunWith(MockitoJUnitRunner.class) @@ -75,7 +93,7 @@ public class BrokerOuterAPITest { private BrokerOuterAPI brokerOuterAPI; public void init() throws Exception { - brokerOuterAPI = new BrokerOuterAPI(new NettyClientConfig(), null); + brokerOuterAPI = new BrokerOuterAPI(new NettyClientConfig()); Field field = BrokerOuterAPI.class.getDeclaredField("remotingClient"); field.setAccessible(true); field.set(brokerOuterAPI, nettyRemotingClient); @@ -91,9 +109,9 @@ public void test_needRegister_normal() throws Exception { 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); + List booleanList = brokerOuterAPI.needRegister(clusterName, brokerAddr, brokerName, brokerId, topicConfigSerializeWrapper, timeOut, false); assertTrue(booleanList.size() > 0); - assertEquals(false, booleanList.contains(Boolean.FALSE)); + assertFalse(booleanList.contains(Boolean.FALSE)); } @Test @@ -119,7 +137,7 @@ public RemotingCommand answer(InvocationOnMock invocation) throws Throwable { return buildResponse(Boolean.TRUE); } }); - List booleanList = brokerOuterAPI.needRegister(clusterName, brokerAddr, brokerName, brokerId, topicConfigSerializeWrapper, timeOut); + List booleanList = brokerOuterAPI.needRegister(clusterName, brokerAddr, brokerName, brokerId, topicConfigSerializeWrapper, timeOut, false); assertEquals(2, booleanList.size()); boolean success = Iterables.any(booleanList, new Predicate() { @@ -128,7 +146,7 @@ public boolean apply(Boolean input) { } }); - assertEquals(true, success); + assertTrue(success); } @@ -144,9 +162,25 @@ public void test_register_normal() throws Exception { 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 registerBrokerResultList = brokerOuterAPI.registerBrokerAll(clusterName, brokerAddr, brokerName, brokerId, "hasServerAddr", topicConfigSerializeWrapper, Lists.newArrayList(), false, timeOut, true); + 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); } @@ -162,26 +196,35 @@ public void test_register_timeout() throws Exception { 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 response; - } else if (invocation.getArgument(0) == nameserver2) { - return response; - } else if (invocation.getArgument(0) == nameserver3) { - TimeUnit.MILLISECONDS.sleep(timeOut + 20); - return response; - } - return response; - } - }); - List registerBrokerResultList = brokerOuterAPI.registerBrokerAll(clusterName, brokerAddr, brokerName, brokerId, "hasServerAddr", topicConfigSerializeWrapper, Lists.newArrayList(), false, timeOut, true); + 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(); @@ -190,4 +233,21 @@ private RemotingCommand buildResponse(Boolean changed) { 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()); + } } diff --git a/broker/src/test/java/org/apache/rocketmq/broker/BrokerPathConfigHelperTest.java b/broker/src/test/java/org/apache/rocketmq/broker/BrokerPathConfigHelperTest.java index 01e7c365928..3b260540838 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/BrokerPathConfigHelperTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/BrokerPathConfigHelperTest.java @@ -17,6 +17,7 @@ package org.apache.rocketmq.broker; +import java.io.File; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -25,18 +26,17 @@ public class BrokerPathConfigHelperTest { @Test public void testGetLmqConsumerOffsetPath() { - String lmqConsumerOffsetPath = BrokerPathConfigHelper.getLmqConsumerOffsetPath("/home/admin/store"); - assertEquals("/home/admin/store/config/lmqConsumerOffset.json", lmqConsumerOffsetPath); + 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 consumerOffsetPath = BrokerPathConfigHelper.getConsumerOffsetPath("/home/admin/store"); - assertEquals("/home/admin/store/config/consumerOffset.json", consumerOffsetPath); + String topicConfigPath = BrokerPathConfigHelper.getTopicConfigPath("/home/admin/store".replace("/", File.separator)); + assertEquals("/home/admin/store/config/topics.json".replace("/", File.separator), topicConfigPath); - String topicConfigPath = BrokerPathConfigHelper.getTopicConfigPath("/home/admin/store"); - assertEquals("/home/admin/store/config/topics.json", topicConfigPath); - - String subscriptionGroupPath = BrokerPathConfigHelper.getSubscriptionGroupPath("/home/admin/store"); - assertEquals("/home/admin/store/config/subscriptionGroup.json", subscriptionGroupPath); + String subscriptionGroupPath = BrokerPathConfigHelper.getSubscriptionGroupPath("/home/admin/store".replace("/", File.separator)); + assertEquals("/home/admin/store/config/subscriptionGroup.json".replace("/", File.separator), subscriptionGroupPath); } } \ No newline at end of file diff --git a/broker/src/test/java/org/apache/rocketmq/broker/BrokerStartupTest.java b/broker/src/test/java/org/apache/rocketmq/broker/BrokerStartupTest.java index c8da08d28df..ce370a39c13 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/BrokerStartupTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/BrokerStartupTest.java @@ -20,6 +20,7 @@ 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; @@ -34,8 +35,21 @@ public void testProperties2SystemEnv() throws NoSuchMethodException, InvocationT Class clazz = BrokerStartup.class; Method method = clazz.getDeclaredMethod("properties2SystemEnv", Properties.class); method.setAccessible(true); - System.setProperty("rocketmq.namesrv.domain", "value"); - method.invoke(null, properties); - Assert.assertEquals("value", System.getProperty("rocketmq.namesrv.domain")); + { + 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")); + } + + } } \ No newline at end of file diff --git a/broker/src/test/java/org/apache/rocketmq/broker/client/ConsumerManagerScannerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/client/ConsumerManagerScannerTest.java new file mode 100644 index 00000000000..d190c0daceb --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/client/ConsumerManagerScannerTest.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/client/ConsumerManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/client/ConsumerManagerTest.java new file mode 100644 index 00000000000..8c909824348 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/client/ConsumerManagerTest.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.HashSet; +import java.util.Set; +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.assertj.core.api.Assertions; +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 ConsumerManagerTest { + + private ClientChannelInfo clientChannelInfo; + + @Mock + private Channel channel; + + private ConsumerManager consumerManager; + + private DefaultConsumerIdsChangeListener defaultConsumerIdsChangeListener; + + @Mock + private BrokerController brokerController; + + @Mock + private ConsumerFilterManager consumerFilterManager; + + private BrokerConfig brokerConfig = new BrokerConfig(); + + private Broker2Client broker2Client; + + private BrokerStatsManager brokerStatsManager; + + 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 = new DefaultConsumerIdsChangeListener(brokerController); + brokerStatsManager = new BrokerStatsManager(brokerConfig); + consumerManager = new ConsumerManager(defaultConsumerIdsChangeListener, brokerStatsManager, brokerConfig); + broker2Client = new Broker2Client(brokerController); + when(brokerController.getConsumerFilterManager()).thenReturn(consumerFilterManager); + when(brokerController.getBrokerConfig()).thenReturn(brokerConfig); + when(brokerController.getBroker2Client()).thenReturn(broker2Client); + } + + @Test + public void compensateBasicConsumerInfoTest() { + ConsumerGroupInfo consumerGroupInfo = consumerManager.getConsumerGroupInfo(GROUP, true); + Assertions.assertThat(consumerGroupInfo).isNull(); + + consumerManager.compensateBasicConsumerInfo(GROUP, ConsumeType.CONSUME_ACTIVELY, MessageModel.BROADCASTING); + consumerGroupInfo = consumerManager.getConsumerGroupInfo(GROUP, true); + Assertions.assertThat(consumerGroupInfo).isNotNull(); + Assertions.assertThat(consumerGroupInfo.getConsumeType()).isEqualTo(ConsumeType.CONSUME_ACTIVELY); + Assertions.assertThat(consumerGroupInfo.getMessageModel()).isEqualTo(MessageModel.BROADCASTING); + } + + @Test + public void compensateSubscribeDataTest() { + ConsumerGroupInfo consumerGroupInfo = consumerManager.getConsumerGroupInfo(GROUP, true); + Assertions.assertThat(consumerGroupInfo).isNull(); + + consumerManager.compensateSubscribeData(GROUP, TOPIC, new SubscriptionData(TOPIC, SubscriptionData.SUB_ALL)); + consumerGroupInfo = consumerManager.getConsumerGroupInfo(GROUP, true); + Assertions.assertThat(consumerGroupInfo).isNotNull(); + Assertions.assertThat(consumerGroupInfo.getSubscriptionTable().size()).isEqualTo(1); + SubscriptionData subscriptionData = consumerGroupInfo.getSubscriptionTable().get(TOPIC); + Assertions.assertThat(subscriptionData).isNotNull(); + Assertions.assertThat(subscriptionData.getTopic()).isEqualTo(TOPIC); + Assertions.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, ConsumeType.CONSUME_PASSIVELY, + MessageModel.BROADCASTING, ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET, subList, true); + Assertions.assertThat(consumerManager.getConsumerTable().get(GROUP)).isNotNull(); + } + + @Test + public void unregisterConsumerTest() { + // register + register(); + + // unregister + consumerManager.unregisterConsumer(GROUP, clientChannelInfo, true); + Assertions.assertThat(consumerManager.getConsumerTable().get(GROUP)).isNull(); + } + + @Test + public void findChannelTest() { + register(); + final ClientChannelInfo consumerManagerChannel = consumerManager.findChannel(GROUP, CLIENT_ID); + Assertions.assertThat(consumerManagerChannel).isNotNull(); + } + + @Test + public void findSubscriptionDataTest() { + register(); + final SubscriptionData subscriptionData = consumerManager.findSubscriptionData(GROUP, TOPIC); + Assertions.assertThat(subscriptionData).isNotNull(); + } + + @Test + public void findSubscriptionDataCountTest() { + register(); + final int count = consumerManager.findSubscriptionDataCount(GROUP); + assert count > 0; + } + + @Test + public void findSubscriptionTest() { + SubscriptionData subscriptionData = consumerManager.findSubscriptionData(GROUP, TOPIC, true); + Assertions.assertThat(subscriptionData).isNull(); + + consumerManager.compensateSubscribeData(GROUP, TOPIC, new SubscriptionData(TOPIC, SubscriptionData.SUB_ALL)); + subscriptionData = consumerManager.findSubscriptionData(GROUP, TOPIC, true); + Assertions.assertThat(subscriptionData).isNotNull(); + Assertions.assertThat(subscriptionData.getTopic()).isEqualTo(TOPIC); + Assertions.assertThat(subscriptionData.getSubString()).isEqualTo(SubscriptionData.SUB_ALL); + + subscriptionData = consumerManager.findSubscriptionData(GROUP, TOPIC, false); + Assertions.assertThat(subscriptionData).isNull(); + } + + @Test + public void scanNotActiveChannelTest() { + clientChannelInfo.setLastUpdateTimestamp(System.currentTimeMillis() - brokerConfig.getChannelExpiredTimeout() * 2); + consumerManager.scanNotActiveChannel(); + Assertions.assertThat(consumerManager.getConsumerTable().size()).isEqualTo(0); + } + + @Test + public void queryTopicConsumeByWhoTest() { + register(); + final HashSet consumeGroup = consumerManager.queryTopicConsumeByWho(TOPIC); + assert consumeGroup.size() > 0; + } + + @Test + public void doChannelCloseEventTest() { + consumerManager.doChannelCloseEvent("127.0.0.1", channel); + assert consumerManager.getConsumerTable().size() == 0; + } + + private void register() { + // register + final Set subList = new HashSet<>(); + SubscriptionData subscriptionData = new SubscriptionData(TOPIC, "*"); + subList.add(subscriptionData); + consumerManager.registerConsumer(GROUP, clientChannelInfo, ConsumeType.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(); + Assertions.assertThat(consumerManager.getConsumerGroupInfo(GROUP, true)).isNotNull(); + Assertions.assertThat(consumerManager.findSubscriptionData(GROUP, TOPIC)).isNull(); + Assertions.assertThat(consumerManager.findSubscriptionData(GROUP, TOPIC + "_1")).isNotNull(); + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/client/ProducerManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/client/ProducerManagerTest.java index 6c794ac5d33..dac5468c875 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/client/ProducerManagerTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/client/ProducerManagerTest.java @@ -21,6 +21,7 @@ import java.lang.reflect.Field; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.junit.Before; import org.junit.Test; @@ -50,25 +51,57 @@ public void init() { @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 CHANNEL_EXPIRED_TIMEOUT = field.getLong(producerManager); - clientInfo.setLastUpdateTimestamp(System.currentTimeMillis() - CHANNEL_EXPIRED_TIMEOUT - 10); + 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(channel)).isNull(); + assertThat(producerManager.getGroupChannelTable().get(group)).isNull(); + assertThat(groupRef.get()).isEqualTo(group); + assertThat(clientChannelInfoRef.get()).isSameAs(clientInfo); assertThat(producerManager.findChannel("clientId")).isNull(); } @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).get(channel)).isNull(); + assertThat(producerManager.getGroupChannelTable().get(group)).isNull(); + assertThat(groupRef.get()).isEqualTo(group); + assertThat(clientChannelInfoRef.get()).isSameAs(clientInfo); assertThat(producerManager.findChannel("clientId")).isNull(); } @@ -86,6 +119,20 @@ public void testRegisterProducer() throws Exception { @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); @@ -95,6 +142,8 @@ public void unregisterProducer() throws Exception { 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(); diff --git a/broker/src/test/java/org/apache/rocketmq/broker/controller/ReplicasManagerRegisterTest.java b/broker/src/test/java/org/apache/rocketmq/broker/controller/ReplicasManagerRegisterTest.java new file mode 100644 index 00000000000..d01a6f76f5e --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/controller/ReplicasManagerRegisterTest.java @@ -0,0 +1,352 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.File; +import java.time.Duration; +import java.util.Arrays; +import java.util.HashSet; +import java.util.UUID; + +import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(ReplicasManager.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<>(Arrays.asList(1L)); + + 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 + ); + Assert.assertEquals(ReplicasManager.RegisterState.REGISTERED, replicasManager0.getRegisterState()); + Assert.assertEquals(1L, replicasManager0.getBrokerControllerId().longValue()); + checkMetadataFile(replicasManager0.getBrokerMetadata(), 1L); + Assert.assertFalse(replicasManager0.getTempBrokerMetadata().isLoaded()); + Assert.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 + ); + Assert.assertEquals(ReplicasManager.RegisterState.REGISTERED, replicasManager0.getRegisterState()); + Assert.assertEquals(1L, replicasManager0.getBrokerControllerId().longValue()); + checkMetadataFile(replicasManager0.getBrokerMetadata(), 1L); + Assert.assertFalse(replicasManager0.getTempBrokerMetadata().isLoaded()); + Assert.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(); + Assert.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(); + Assert.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(); + + Assert.assertEquals(ReplicasManager.State.FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE, replicasManager.getState()); + Assert.assertEquals(ReplicasManager.RegisterState.INITIAL, replicasManager.getRegisterState()); + Assert.assertFalse(replicasManager.getTempBrokerMetadata().fileExists()); + Assert.assertFalse(replicasManager.getBrokerMetadata().fileExists()); + Assert.assertNull(replicasManager.getBrokerControllerId()); + replicasManager.shutdown(); + } + + @Test + public void testRegisterFailedAtCreateTempFile() 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())).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 spyReplicasManager = PowerMockito.spy(replicasManager); + PowerMockito.doReturn(false).when(spyReplicasManager, "createTempMetadataFile", anyLong()); + + spyReplicasManager.start(); + + Assert.assertEquals(ReplicasManager.State.FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE, spyReplicasManager.getState()); + Assert.assertEquals(ReplicasManager.RegisterState.INITIAL, spyReplicasManager.getRegisterState()); + Assert.assertFalse(spyReplicasManager.getTempBrokerMetadata().fileExists()); + Assert.assertFalse(spyReplicasManager.getBrokerMetadata().fileExists()); + Assert.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()); + 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.start(); + + Assert.assertEquals(ReplicasManager.State.FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE, replicasManager.getState()); + Assert.assertNotEquals(ReplicasManager.RegisterState.CREATE_METADATA_FILE_DONE, replicasManager.getRegisterState()); + Assert.assertNotEquals(ReplicasManager.RegisterState.REGISTERED, replicasManager.getRegisterState()); + + replicasManager.shutdown(); + + Assert.assertFalse(replicasManager.getBrokerMetadata().fileExists()); + Assert.assertNull(replicasManager.getBrokerControllerId()); + } + + @Test + public void testRegisterFailedAtCreateMetadataFileAndDeleteTemp() 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())).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 spyReplicasManager = PowerMockito.spy(replicasManager); + PowerMockito.doReturn(false).when(spyReplicasManager, "createMetadataFileAndDeleteTemp"); + + spyReplicasManager.start(); + + Assert.assertEquals(ReplicasManager.State.FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE, spyReplicasManager.getState()); + Assert.assertEquals(ReplicasManager.RegisterState.CREATE_TEMP_METADATA_FILE_DONE, spyReplicasManager.getRegisterState()); + TempBrokerMetadata tempBrokerMetadata = spyReplicasManager.getTempBrokerMetadata(); + Assert.assertTrue(tempBrokerMetadata.fileExists()); + Assert.assertTrue(tempBrokerMetadata.isLoaded()); + Assert.assertFalse(spyReplicasManager.getBrokerMetadata().fileExists()); + Assert.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); + // because apply brokerId: 1 has succeeded, so now next broker id is 2 + when(mockedBrokerOuterAPI.getNextBrokerId(any(), any(), any())).thenReturn(new GetNextBrokerIdResponseHeader(CLUSTER_NAME, BROKER_NAME, 2L)); + + replicasManagerNew.start(); + + Assert.assertEquals(ReplicasManager.State.RUNNING, replicasManagerNew.getState()); + Assert.assertEquals(ReplicasManager.RegisterState.REGISTERED, replicasManagerNew.getRegisterState()); + // tempMetadata has been cleared + Assert.assertFalse(replicasManagerNew.getTempBrokerMetadata().fileExists()); + Assert.assertFalse(replicasManagerNew.getTempBrokerMetadata().isLoaded()); + // metadata has been persisted + Assert.assertTrue(replicasManagerNew.getBrokerMetadata().fileExists()); + Assert.assertTrue(replicasManagerNew.getBrokerMetadata().isLoaded()); + Assert.assertEquals(1L, replicasManagerNew.getBrokerMetadata().getBrokerId().longValue()); + Assert.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(); + + Assert.assertEquals(ReplicasManager.State.FIRST_TIME_SYNC_CONTROLLER_METADATA_DONE, replicasManager.getState()); + Assert.assertEquals(ReplicasManager.RegisterState.CREATE_METADATA_FILE_DONE, replicasManager.getRegisterState()); + TempBrokerMetadata tempBrokerMetadata = replicasManager.getTempBrokerMetadata(); + // temp metadata has been cleared + Assert.assertFalse(tempBrokerMetadata.fileExists()); + Assert.assertFalse(tempBrokerMetadata.isLoaded()); + // metadata has been persisted + Assert.assertTrue(replicasManager.getBrokerMetadata().fileExists()); + Assert.assertTrue(replicasManager.getBrokerMetadata().isLoaded()); + Assert.assertEquals(1L, replicasManager.getBrokerMetadata().getBrokerId().longValue()); + Assert.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); + // because apply brokerId: 1 has succeeded, so now next broker id is 2 + when(mockedBrokerOuterAPI.getNextBrokerId(any(), any(), any())).thenReturn(new GetNextBrokerIdResponseHeader(CLUSTER_NAME, BROKER_NAME, 2L)); + // because apply brokerId: 1 has succeeded, so next request which try to apply brokerId: 1 will be failed + when(mockedBrokerOuterAPI.applyBrokerId(any(), any(), eq(1L), any(), any())).thenThrow(new RuntimeException()); + when(mockedBrokerOuterAPI.registerBrokerToController(any(), any(), anyLong(), any(), any())).thenReturn(new Pair<>(new RegisterBrokerToControllerResponseHeader(), syncStateSet)); + replicasManagerNew.start(); + + Assert.assertEquals(ReplicasManager.State.RUNNING, replicasManagerNew.getState()); + Assert.assertEquals(ReplicasManager.RegisterState.REGISTERED, replicasManagerNew.getRegisterState()); + // tempMetadata has been cleared + Assert.assertFalse(replicasManagerNew.getTempBrokerMetadata().fileExists()); + Assert.assertFalse(replicasManagerNew.getTempBrokerMetadata().isLoaded()); + // metadata has been persisted + Assert.assertTrue(replicasManagerNew.getBrokerMetadata().fileExists()); + Assert.assertTrue(replicasManagerNew.getBrokerMetadata().isLoaded()); + Assert.assertEquals(1L, replicasManagerNew.getBrokerMetadata().getBrokerId().longValue()); + Assert.assertEquals(1L, replicasManagerNew.getBrokerControllerId().longValue()); + replicasManagerNew.shutdown(); + } + + + private void checkMetadataFile(BrokerMetadata brokerMetadata0 ,Long brokerId) throws Exception { + Assert.assertEquals(brokerId, brokerMetadata0.getBrokerId()); + Assert.assertTrue(brokerMetadata0.fileExists()); + BrokerMetadata brokerMetadata = new BrokerMetadata(brokerMetadata0.getFilePath()); + brokerMetadata.readFromFile(); + Assert.assertEquals(brokerMetadata0, brokerMetadata); + } + + @After + public void clear() { + UtilAll.deleteFile(new File(STORE_BASE_PATH)); + } + + +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/controller/ReplicasManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/controller/ReplicasManagerTest.java new file mode 100644 index 00000000000..c863f7ac96c --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/controller/ReplicasManagerTest.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.io.File; +import java.util.Arrays; +import java.util.HashSet; +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.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.ElectMasterResponseHeader; +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.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(); + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/failover/EscapeBridgeTest.java b/broker/src/test/java/org/apache/rocketmq/broker/failover/EscapeBridgeTest.java new file mode 100644 index 00000000000..d7bd753d776 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/failover/EscapeBridgeTest.java @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; +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.producer.DefaultMQProducer; +import org.apache.rocketmq.common.BrokerConfig; +import org.apache.rocketmq.common.message.MessageExtBrokerInner; +import org.apache.rocketmq.store.DefaultMessageStore; +import org.apache.rocketmq.store.GetMessageResult; +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.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.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.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class EscapeBridgeTest { + + private EscapeBridge escapeBridge; + + @Mock + private BrokerController brokerController; + + @Mock + private MessageExtBrokerInner messageExtBrokerInner; + + private BrokerConfig brokerConfig; + + @Mock + private DefaultMessageStore defaultMessageStore; + + private GetMessageResult getMessageResult; + + @Mock + private DefaultMQProducer defaultMQProducer; + + 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)); + + TopicRouteInfoManager topicRouteInfoManager = mock(TopicRouteInfoManager.class); + when(brokerController.getTopicRouteInfoManager()).thenReturn(topicRouteInfoManager); + when(topicRouteInfoManager.findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean())).thenReturn(""); + + BrokerOuterAPI brokerOuterAPI = mock(BrokerOuterAPI.class); + when(brokerController.getBrokerOuterAPI()).thenReturn(brokerOuterAPI); + when(brokerOuterAPI.pullMessageFromSpecificBrokerAsync(anyString(), anyString(), anyString(), anyString(), anyInt(), anyLong(), anyInt(), anyLong())) + .thenReturn(CompletableFuture.completedFuture(new PullResult(PullStatus.FOUND, -1, -1, -1, new ArrayList<>()))); + + 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 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 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(); + } + +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/filter/CommitLogDispatcherCalcBitMapTest.java b/broker/src/test/java/org/apache/rocketmq/broker/filter/CommitLogDispatcherCalcBitMapTest.java index 8f28832c0c7..af1d06ea28d 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/filter/CommitLogDispatcherCalcBitMapTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/filter/CommitLogDispatcherCalcBitMapTest.java @@ -55,7 +55,7 @@ public void testDispatch_filterDataIllegal() { filterManager); for (int i = 0; i < 1; i++) { - Map properties = new HashMap(4); + Map properties = new HashMap<>(4); properties.put("a", String.valueOf(i * 10 + 5)); String topic = "topic" + i; @@ -99,7 +99,7 @@ public void testDispatch_blankFilterData() { filterManager); for (int i = 0; i < 10; i++) { - Map properties = new HashMap(4); + Map properties = new HashMap<>(4); properties.put("a", String.valueOf(i * 10 + 5)); String topic = "topic" + i; @@ -136,7 +136,7 @@ public void testDispatch() { filterManager); for (int i = 0; i < 10; i++) { - Map properties = new HashMap(4); + Map properties = new HashMap<>(4); properties.put("a", String.valueOf(i * 10 + 5)); String topic = "topic" + i; diff --git a/broker/src/test/java/org/apache/rocketmq/broker/filter/ConsumerFilterManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/filter/ConsumerFilterManagerTest.java index 68d60092d8b..c01d8299dcf 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/filter/ConsumerFilterManagerTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/filter/ConsumerFilterManagerTest.java @@ -17,17 +17,16 @@ package org.apache.rocketmq.broker.filter; -import org.apache.rocketmq.common.UtilAll; -import org.apache.rocketmq.common.filter.ExpressionType; -import org.apache.rocketmq.common.filter.FilterAPI; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; -import org.junit.Test; - 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; @@ -81,9 +80,7 @@ public void testRegister_newExpressionCompileErrorAndRemoveOld() { public void testRegister_change() { ConsumerFilterManager filterManager = gen(10, 10); - ConsumerFilterData filterData = filterManager.get("topic9", "CID_9"); - - System.out.println(filterData.getCompiledExpression()); + ConsumerFilterData filterData; String newExpr = "a > 0 and a < 10"; @@ -92,8 +89,6 @@ public void testRegister_change() { filterData = filterManager.get("topic9", "CID_9"); assertThat(newExpr).isEqualTo(filterData.getExpression()); - - System.out.println(filterData.toString()); } @Test diff --git a/broker/src/test/java/org/apache/rocketmq/broker/filter/MessageStoreWithFilterTest.java b/broker/src/test/java/org/apache/rocketmq/broker/filter/MessageStoreWithFilterTest.java index 23b38e25d92..84bca916998 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/filter/MessageStoreWithFilterTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/filter/MessageStoreWithFilterTest.java @@ -17,12 +17,25 @@ 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.protocol.heartbeat.SubscriptionData; +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; @@ -30,42 +43,34 @@ import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; import org.apache.rocketmq.store.MessageArrivingListener; -import org.apache.rocketmq.store.MessageExtBrokerInner; 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 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 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[] msgBody = msg.getBytes(); + 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 queueId = 0; - private static final String storePath = "." + File.separator + "unit_test_store"; - private static final int commitLogFileSize = 1024 * 1024 * 256; - private static final int cqFileSize = 300000 * 20; - private static final int cqExtFileSize = 300000 * 128; + 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 bornHost; - private static SocketAddress StoreHost; + private static SocketAddress storeHost; private DefaultMessageStore master; @@ -77,11 +82,11 @@ public class MessageStoreWithFilterTest { static { try { - StoreHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); + storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); } catch (UnknownHostException e) { } try { - BornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); + bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); } catch (UnknownHostException e) { } } @@ -94,23 +99,25 @@ public void init() throws Exception { @After public void destroy() { - master.shutdown(); - master.destroy(); - UtilAll.deleteFile(new File(storePath)); + if (master != null) { + master.shutdown(); + master.destroy(); + } + UtilAll.deleteFile(new File(STORE_PATH)); } public MessageExtBrokerInner buildMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); - msg.setTopic(topic); + msg.setTopic(TOPIC); msg.setTags(System.currentTimeMillis() + "TAG"); msg.setKeys("Hello"); - msg.setBody(msgBody); + msg.setBody(MSG_BODY); msg.setKeys(String.valueOf(System.currentTimeMillis())); - msg.setQueueId(queueId); + msg.setQueueId(QUEUE_ID); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); - msg.setStoreHost(StoreHost); - msg.setBornHost(BornHost); + msg.setStoreHost(storeHost); + msg.setBornHost(bornHost); for (int i = 1; i < 3; i++) { msg.putUserProperty(String.valueOf(i), "imagoodperson" + i); } @@ -128,15 +135,15 @@ public MessageStoreConfig buildStoreConfig(int commitLogFileSize, int cqFileSize messageStoreConfig.setMessageIndexEnable(false); messageStoreConfig.setEnableConsumeQueueExt(enableCqExt); - messageStoreConfig.setStorePathRootDir(storePath); - messageStoreConfig.setStorePathCommitLog(storePath + File.separator + "commitlog"); + messageStoreConfig.setStorePathRootDir(STORE_PATH); + messageStoreConfig.setStorePathCommitLog(STORE_PATH + File.separator + "commitlog"); return messageStoreConfig; } protected DefaultMessageStore gen(ConsumerFilterManager filterManager) throws Exception { MessageStoreConfig messageStoreConfig = buildStoreConfig( - commitLogFileSize, cqFileSize, true, cqExtFileSize + COMMIT_LOG_FILE_SIZE, CQ_FILE_SIZE, true, CQ_EXT_FILE_SIZE ); BrokerConfig brokerConfig = new BrokerConfig(); @@ -153,7 +160,7 @@ public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { } } - , brokerConfig); + , brokerConfig, new ConcurrentHashMap<>()); master.getDispatcherList().addFirst(new CommitLogDispatcher() { @Override @@ -166,7 +173,11 @@ public void dispatch(DispatchRequest request) { }); master.getDispatcherList().addFirst(new CommitLogDispatcherCalcBitMap(brokerConfig, filterManager)); - assertThat(master.load()).isTrue(); + if (MixAll.isWindows()) { + Assume.assumeTrue(master.load()); + } else { + assertThat(master.load()).isTrue(); + } master.start(); @@ -175,9 +186,9 @@ public void dispatch(DispatchRequest request) { protected List putMsg(DefaultMessageStore master, int topicCount, int msgCountPerTopic) throws Exception { - List msgs = new ArrayList(); + List msgs = new ArrayList<>(); for (int i = 0; i < topicCount; i++) { - String realTopic = topic + i; + String realTopic = TOPIC + i; for (int j = 0; j < msgCountPerTopic; j++) { MessageExtBrokerInner msg = buildMessage(); msg.setTopic(realTopic); @@ -196,7 +207,7 @@ protected List putMsg(DefaultMessageStore master, int top } protected List filtered(List msgs, ConsumerFilterData filterData) { - List filteredMsgs = new ArrayList(); + List filteredMsgs = new ArrayList<>(); for (MessageExtBrokerInner messageExtBrokerInner : msgs) { @@ -242,7 +253,7 @@ public void testGetMessage_withFilterBitMapAndConsumerChanged() throws Exception resetGroup, resetSubData.getSubString(), resetSubData.getExpressionType(), System.currentTimeMillis()); - GetMessageResult resetGetResult = master.getMessage(resetGroup, topic, queueId, 0, 1000, + GetMessageResult resetGetResult = master.getMessage(resetGroup, topic, QUEUE_ID, 0, 1000, new ExpressionMessageFilter(resetSubData, resetFilterData, filterManager)); try { @@ -269,7 +280,7 @@ public void testGetMessage_withFilterBitMapAndConsumerChanged() throws Exception List filteredMsgs = filtered(msgs, normalFilterData); - GetMessageResult normalGetResult = master.getMessage(normalGroup, topic, queueId, 0, 1000, + GetMessageResult normalGetResult = master.getMessage(normalGroup, topic, QUEUE_ID, 0, 1000, new ExpressionMessageFilter(normalSubData, normalFilterData, filterManager)); try { @@ -288,7 +299,7 @@ public void testGetMessage_withFilterBitMap() throws Exception { Thread.sleep(100); for (int i = 0; i < topicCount; i++) { - String realTopic = topic + i; + String realTopic = TOPIC + i; for (int j = 0; j < msgPerTopic; j++) { String group = "CID_" + j; @@ -304,7 +315,7 @@ public void testGetMessage_withFilterBitMap() throws Exception { subscriptionData.setClassFilterMode(false); subscriptionData.setSubString(filterData.getExpression()); - GetMessageResult getMessageResult = master.getMessage(group, realTopic, queueId, 0, 10000, + GetMessageResult getMessageResult = master.getMessage(group, realTopic, QUEUE_ID, 0, 10000, new ExpressionMessageFilter(subscriptionData, filterData, filterManager)); String assertMsg = group + "-" + realTopic; try { @@ -347,27 +358,31 @@ public void testGetMessage_withFilterBitMap() throws Exception { public void testGetMessage_withFilter_checkTagsCode() throws Exception { putMsg(master, topicCount, msgPerTopic); - Thread.sleep(200); - - for (int i = 0; i < topicCount; i++) { - String realTopic = topic + i; - - GetMessageResult getMessageResult = master.getMessage("test", realTopic, queueId, 0, 10000, - new MessageFilter() { - @Override - public boolean isMatchedByConsumeQueue(Long tagsCode, ConsumeQueueExt.CqExtUnit cqExtUnit) { - if (tagsCode != null && tagsCode <= ConsumeQueueExt.MAX_ADDR) { - return false; - } - return true; - } + 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); - } + @Override + public boolean isMatchedByCommitLog(ByteBuffer msgBuffer, + Map properties) { + return true; + } + }); + assertThat(getMessageResult.getMessageCount()).isEqualTo(msgPerTopic); + } + } + }); } } diff --git a/broker/src/test/java/org/apache/rocketmq/broker/longpolling/PullRequestHoldServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/longpolling/PullRequestHoldServiceTest.java new file mode 100644 index 00000000000..6eeb4adbe34 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/longpolling/PullRequestHoldServiceTest.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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(); + } + +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManagerTest.java new file mode 100644 index 00000000000..11f7ae8215a --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManagerTest.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BrokerMetricsManagerTest { + + @Test + public void testNewAttributesBuilder() { + Attributes attributes = BrokerMetricsManager.newAttributesBuilder().put("a", "b") + .build(); + assertThat(attributes.get(AttributeKey.stringKey("a"))).isEqualTo("b"); + } + + @Test + public void testCustomizedAttributesBuilder() { + BrokerMetricsManager.attributesBuilderSupplier = () -> 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 = BrokerMetricsManager.newAttributesBuilder().put("a", "b") + .build(); + assertThat(attributes.get(AttributeKey.stringKey("a"))).isEqualTo("b"); + assertThat(attributes.get(AttributeKey.stringKey("customized"))).isEqualTo("value"); + } +} \ No newline at end of file diff --git a/broker/src/test/java/org/apache/rocketmq/broker/offset/BroadcastOffsetManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/offset/BroadcastOffsetManagerTest.java new file mode 100644 index 00000000000..9dc00f9d6b1 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/offset/BroadcastOffsetManagerTest.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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() { + 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() { + // 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(); + }); + } +} \ No newline at end of file diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/LevelTest.java b/broker/src/test/java/org/apache/rocketmq/broker/offset/BroadcastOffsetStoreTest.java similarity index 69% rename from logging/src/test/java/org/apache/rocketmq/logging/inner/LevelTest.java rename to broker/src/test/java/org/apache/rocketmq/broker/offset/BroadcastOffsetStoreTest.java index 21667e1480f..ef830b9e9c4 100644 --- a/logging/src/test/java/org/apache/rocketmq/logging/inner/LevelTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/offset/BroadcastOffsetStoreTest.java @@ -14,24 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.apache.rocketmq.logging.inner; +package org.apache.rocketmq.broker.offset; import org.junit.Assert; import org.junit.Test; -public class LevelTest { - - @Test - public void levelTest() { - Level info = Level.toLevel("info"); - Level error = Level.toLevel(3); - Assert.assertTrue(error != null && info != null); - } +public class BroadcastOffsetStoreTest { @Test - public void loggerLevel(){ - Level level = Logger.getRootLogger().getLevel(); - Assert.assertTrue(level!=null); + public void testBasicOffsetStore() { + BroadcastOffsetStore offsetStore = new BroadcastOffsetStore(); + offsetStore.updateOffset(0, 100L, false); + offsetStore.updateOffset(1, 200L, false); + Assert.assertEquals(100L, offsetStore.readOffset(0)); } -} +} \ No newline at end of file diff --git a/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManagerTest.java new file mode 100644 index 00000000000..7bd289a6f11 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManagerTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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.assertj.core.api.Assertions.assertThat; + +public class ConsumerOffsetManagerTest { + + private static final String KEY = "FooBar@FooBarGroup"; + + private BrokerController brokerController; + + private ConsumerOffsetManager consumerOffsetManager; + + @Before + 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 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); + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoManagerLockFreeNotifyTest.java b/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoManagerLockFreeNotifyTest.java new file mode 100644 index 00000000000..93689efa586 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoManagerLockFreeNotifyTest.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.concurrent.atomic.AtomicBoolean; +import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.broker.processor.PopMessageProcessor; +import org.apache.rocketmq.common.BrokerConfig; +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 ConsumerOrderInfoManager 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() { + 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 ConsumerOrderInfoManager(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() { + ConsumerOrderInfoManager savedConsumerOrderInfoManager = new ConsumerOrderInfoManager(); + 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()); + } +} \ No newline at end of file diff --git a/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoManagerTest.java new file mode 100644 index 00000000000..25b418c9344 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoManagerTest.java @@ -0,0 +1,537 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Map; +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.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.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; +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 ConsumerOrderInfoManager consumerOrderInfoManager; + + @Before + public void before() { + consumerOrderInfoManager = new ConsumerOrderInfoManager(); + 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); + ConcurrentMap subscriptionGroupConfigConcurrentMap = new ConcurrentHashMap<>(); + subscriptionGroupConfigConcurrentMap.put(GROUP, new SubscriptionGroupConfig()); + when(subscriptionGroupManager.getSubscriptionGroupTable()).thenReturn(subscriptionGroupConfigConcurrentMap); + + TopicConfig topicConfig = new TopicConfig(TOPIC); + when(topicConfigManager.selectTopicConfig(eq(TOPIC))).thenReturn(topicConfig); + + ConsumerOrderInfoManager consumerOrderInfoManager = new ConsumerOrderInfoManager(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() { + ConsumerOrderInfoManager.OrderInfo prevOrderInfo = consumerOrderInfoManager.getTable().values().stream().findFirst() + .get().get(QUEUE_ID_0); + + String dataEncoded = consumerOrderInfoManager.encode(); + + consumerOrderInfoManager.decode(dataEncoded); + ConsumerOrderInfoManager.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()); + ConsumerOrderInfoManager.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)); + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/offset/LmqConsumerOffsetManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/offset/LmqConsumerOffsetManagerTest.java index 6ec20c618df..9626bcaaeeb 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/offset/LmqConsumerOffsetManagerTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/offset/LmqConsumerOffsetManagerTest.java @@ -19,16 +19,15 @@ 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.common.subscription.SubscriptionGroupConfig; 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; @@ -73,9 +72,36 @@ public void testOffsetManage() { 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())); } -} \ No newline at end of file +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/pagecache/ManyMessageTransferTest.java b/broker/src/test/java/org/apache/rocketmq/broker/pagecache/ManyMessageTransferTest.java index 508635c044c..2617b5cee9f 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/pagecache/ManyMessageTransferTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/pagecache/ManyMessageTransferTest.java @@ -25,7 +25,7 @@ public class ManyMessageTransferTest { @Test - public void ManyMessageTransferBuilderTest(){ + public void ManyMessageTransferBuilderTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); GetMessageResult getMessageResult = new GetMessageResult(); @@ -33,7 +33,7 @@ public void ManyMessageTransferBuilderTest(){ } @Test - public void ManyMessageTransferPosTest(){ + public void ManyMessageTransferPosTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); GetMessageResult getMessageResult = new GetMessageResult(); @@ -42,7 +42,7 @@ public void ManyMessageTransferPosTest(){ } @Test - public void ManyMessageTransferCountTest(){ + public void ManyMessageTransferCountTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); GetMessageResult getMessageResult = new GetMessageResult(); @@ -53,7 +53,7 @@ public void ManyMessageTransferCountTest(){ } @Test - public void ManyMessageTransferCloseTest(){ + public void ManyMessageTransferCloseTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); GetMessageResult getMessageResult = new GetMessageResult(); diff --git a/broker/src/test/java/org/apache/rocketmq/broker/pagecache/OneMessageTransferTest.java b/broker/src/test/java/org/apache/rocketmq/broker/pagecache/OneMessageTransferTest.java index 2cd4bdc1657..1930641d7b6 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/pagecache/OneMessageTransferTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/pagecache/OneMessageTransferTest.java @@ -18,35 +18,35 @@ package org.apache.rocketmq.broker.pagecache; import java.nio.ByteBuffer; -import org.apache.rocketmq.store.MappedFile; 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(){ + public void OneMessageTransferTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); - SelectMappedBufferResult selectMappedBufferResult = new SelectMappedBufferResult(0,byteBuffer,20,new MappedFile()); + SelectMappedBufferResult selectMappedBufferResult = new SelectMappedBufferResult(0,byteBuffer,20,new DefaultMappedFile()); OneMessageTransfer manyMessageTransfer = new OneMessageTransfer(byteBuffer,selectMappedBufferResult); } @Test - public void OneMessageTransferCountTest(){ + public void OneMessageTransferCountTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); - SelectMappedBufferResult selectMappedBufferResult = new SelectMappedBufferResult(0,byteBuffer,20,new MappedFile()); + SelectMappedBufferResult selectMappedBufferResult = new SelectMappedBufferResult(0,byteBuffer,20,new DefaultMappedFile()); OneMessageTransfer manyMessageTransfer = new OneMessageTransfer(byteBuffer,selectMappedBufferResult); Assert.assertEquals(manyMessageTransfer.count(),40); } @Test - public void OneMessageTransferPosTest(){ + public void OneMessageTransferPosTest() { ByteBuffer byteBuffer = ByteBuffer.allocate(20); byteBuffer.putInt(20); - SelectMappedBufferResult selectMappedBufferResult = new SelectMappedBufferResult(0,byteBuffer,20,new MappedFile()); + SelectMappedBufferResult selectMappedBufferResult = new SelectMappedBufferResult(0,byteBuffer,20,new DefaultMappedFile()); OneMessageTransfer manyMessageTransfer = new OneMessageTransfer(byteBuffer,selectMappedBufferResult); Assert.assertEquals(manyMessageTransfer.position(),8); } diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/AckMessageProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/AckMessageProcessorTest.java new file mode 100644 index 00000000000..c0afb46c330 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/AckMessageProcessorTest.java @@ -0,0 +1,361 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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.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 { + 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); + 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); + } + } + +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java index a2abdc02fed..fa2d929b0c9 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java @@ -20,51 +20,60 @@ import com.google.common.collect.Sets; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.LongAdder; 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.offset.ConsumerOffsetManager; +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.TopicFilterType; +import org.apache.rocketmq.common.TopicQueueId; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; 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.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.body.LockBatchRequestBody; -import org.apache.rocketmq.common.protocol.body.UnlockBatchRequestBody; -import org.apache.rocketmq.common.protocol.header.CreateTopicRequestHeader; -import org.apache.rocketmq.common.protocol.header.DeleteTopicRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetAllTopicConfigResponseHeader; -import org.apache.rocketmq.common.protocol.header.GetEarliestMsgStoretimeRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetMaxOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetMinOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.ResumeCheckHalfMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.SearchOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig; 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.store.AppendMessageResult; -import org.apache.rocketmq.store.AppendMessageStatus; +import org.apache.rocketmq.remoting.protocol.RequestCode; +import org.apache.rocketmq.remoting.protocol.ResponseCode; +import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody; +import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody; +import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.DeleteTopicRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.GetAllTopicConfigResponseHeader; +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.GetTopicConfigRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.ResumeCheckHalfMessageRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.SearchOffsetRequestHeader; +import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; +import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; +import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.store.DefaultMessageStore; -import org.apache.rocketmq.store.MappedFile; -import org.apache.rocketmq.store.MessageExtBrokerInner; 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.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; -import org.apache.rocketmq.store.schedule.ScheduleMessageService; +import org.apache.rocketmq.store.logfile.DefaultMappedFile; import org.apache.rocketmq.store.stats.BrokerStats; import org.junit.Before; import org.junit.Test; @@ -73,13 +82,6 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; -import java.net.SocketAddress; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -96,18 +98,26 @@ public class AdminBrokerProcessorTest { @Mock private ChannelHandlerContext handlerContext; + @Mock + private Channel channel; + @Spy private BrokerController - brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), + brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); @Mock private MessageStore messageStore; - private Set systemTopicSet; + @Mock + private SendMessageProcessor sendMessageProcessor; @Mock - private Channel channel; + private ConcurrentMap inFlyWritingCouterMap; + + private Set systemTopicSet; + private String topic; + @Mock private SocketAddress socketAddress; @Mock @@ -124,29 +134,36 @@ public class AdminBrokerProcessorTest { private ScheduleMessageService scheduleMessageService; @Before - public void init() { + public void init() throws Exception { brokerController.setMessageStore(messageStore); + + //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); + 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); } @Test public void testProcessRequest_success() throws RemotingCommandException, UnknownHostException { - RemotingCommand request = createResumeCheckHalfMessageCommand(); - when(messageStore.selectOneMessageByOffset(any(Long.class))).thenReturn(createSelectMappedBufferResult()); - when(messageStore.putMessage(any(MessageExtBrokerInner.class))).thenReturn(new PutMessageResult - (PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); + RemotingCommand request = createUpdateBrokerConfigCommand(); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @@ -155,8 +172,6 @@ public void testProcessRequest_success() throws RemotingCommandException, Unknow public void testProcessRequest_fail() throws RemotingCommandException, UnknownHostException { RemotingCommand request = createResumeCheckHalfMessageCommand(); when(messageStore.selectOneMessageByOffset(any(Long.class))).thenReturn(createSelectMappedBufferResult()); - when(messageStore.putMessage(any(MessageExtBrokerInner.class))).thenReturn(new PutMessageResult - (PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR))); RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); } @@ -184,6 +199,23 @@ public void testUpdateAndCreateTopic() throws Exception { } + @Test + public void testUpdateAndCreateTopicOnSlave() throws Exception { + // setup + MessageStoreConfig messageStoreConfig = mock(MessageStoreConfig.class); + when(messageStoreConfig.getBrokerRole()).thenReturn(BrokerRole.SLAVE); + defaultMessageStore = mock(DefaultMessageStore.class); + when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); + + // test on slave + String topic = "TEST_CREATE_TOPIC"; + RemotingCommand request = buildCreateTopicRequest(topic); + RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); + assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); + assertThat(response.getRemark()).isEqualTo("Can't modify topic or subscription group from slave broker, " + + "please execute it from master broker."); + } + @Test public void testDeleteTopic() throws Exception { //test system topic @@ -200,6 +232,22 @@ public void testDeleteTopic() throws Exception { assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } + @Test + public void testDeleteTopicOnSlave() throws Exception { + // setup + MessageStoreConfig messageStoreConfig = mock(MessageStoreConfig.class); + when(messageStoreConfig.getBrokerRole()).thenReturn(BrokerRole.SLAVE); + defaultMessageStore = mock(DefaultMessageStore.class); + when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); + + String topic = "TEST_DELETE_TOPIC"; + RemotingCommand request = buildDeleteTopicRequest(topic); + RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request); + assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); + assertThat(response.getRemark()).isEqualTo("Can't modify topic or subscription group from slave broker, " + + "please execute it from master broker."); + } + @Test public void testGetAllTopicConfig() throws Exception { GetAllTopicConfigResponseHeader getAllTopicConfigResponseHeader = new GetAllTopicConfigResponseHeader(); @@ -230,6 +278,36 @@ public void testGetBrokerConfig() throws Exception { 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 path"); + + } + @Test public void testSearchOffsetByTimestamp() throws Exception { messageStore = mock(MessageStore.class); @@ -336,6 +414,30 @@ public void testUpdateAndCreateSubscriptionGroup() throws RemotingCommandExcepti assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } + @Test + public void testUpdateAndCreateSubscriptionGroupOnSlave() throws RemotingCommandException { + // Setup + MessageStoreConfig messageStoreConfig = mock(MessageStoreConfig.class); + when(messageStoreConfig.getBrokerRole()).thenReturn(BrokerRole.SLAVE); + defaultMessageStore = mock(DefaultMessageStore.class); + when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); + + // Test + 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.SYSTEM_ERROR); + assertThat(response.getRemark()).isEqualTo("Can't modify topic or subscription group from slave broker, " + + "please execute it from master broker."); + } + @Test public void testGetAllSubscriptionGroup() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG, null); @@ -352,6 +454,24 @@ public void testDeleteSubscriptionGroup() throws RemotingCommandException { assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } + @Test + public void testDeleteSubscriptionGroupOnSlave() throws RemotingCommandException { + // Setup + MessageStoreConfig messageStoreConfig = mock(MessageStoreConfig.class); + when(messageStoreConfig.getBrokerRole()).thenReturn(BrokerRole.SLAVE); + defaultMessageStore = mock(DefaultMessageStore.class); + when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); + + // Test + 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.SYSTEM_ERROR); + assertThat(response.getRemark()).isEqualTo("Can't modify topic or subscription group from slave broker, " + + "please execute it from master broker."); + } + @Test public void testGetTopicStatsInfo() throws RemotingCommandException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TOPIC_STATS_INFO, null); @@ -387,6 +507,13 @@ public void testGetProducerConnectionList() throws RemotingCommandException { 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); @@ -411,14 +538,40 @@ public void testGetAllConsumerOffset() throws RemotingCommandException { public void testGetAllDelayOffset() throws Exception { defaultMessageStore = mock(DefaultMessageStore.class); scheduleMessageService = mock(ScheduleMessageService.class); - when(brokerController.getMessageStore()).thenReturn(defaultMessageStore); - when(defaultMessageStore.getScheduleMessageService()).thenReturn(scheduleMessageService); +// 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 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."); + } + } + private RemotingCommand buildCreateTopicRequest(String topic) { CreateTopicRequestHeader requestHeader = new CreateTopicRequestHeader(); requestHeader.setTopic(topic); @@ -454,7 +607,7 @@ private MessageExt createDefaultMessageExt() { } private SelectMappedBufferResult createSelectMappedBufferResult() { - SelectMappedBufferResult result = new SelectMappedBufferResult(0, ByteBuffer.allocate(1024), 0, new MappedFile()); + SelectMappedBufferResult result = new SelectMappedBufferResult(0, ByteBuffer.allocate(1024), 0, new DefaultMappedFile()); return result; } @@ -470,4 +623,10 @@ private RemotingCommand createResumeCheckHalfMessageCommand() { request.makeCustomHeaderToNet(); return request; } + + private RemotingCommand createUpdateBrokerConfigCommand() { + RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_BROKER_CONFIG, null); + request.makeCustomHeaderToNet(); + return request; + } } diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessorTest.java new file mode 100644 index 00000000000..e51e110a7a8 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessorTest.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 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.common.BrokerConfig; +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.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.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.apache.rocketmq.broker.processor.PullMessageProcessorTest.createConsumerData; +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 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 = new EscapeBridge(this.brokerController); + + @Before + public void init() throws IllegalAccessException, NoSuchFieldException { + brokerController.setMessageStore(messageStore); + Field field = BrokerController.class.getDeclaredField("broker2Client"); + field.setAccessible(true); + field.set(brokerController, broker2Client); + + Field ebField = BrokerController.class.getDeclaredField("escapeBridge"); + ebField.setAccessible(true); + ebField.set(brokerController, this.escapeBridge); + + Channel mockChannel = mock(Channel.class); + when(handlerContext.channel()).thenReturn(mockChannel); + brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig()); + 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); + changeInvisibleTimeProcessor = new ChangeInvisibleTimeProcessor(brokerController); + } + + @Test + public void testProcessRequest_Success() throws RemotingCommandException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException { + when(escapeBridge.putMessageToSpecificQueue(any(MessageExtBrokerInner.class))).thenReturn(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()); + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/ClientManageProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/ClientManageProcessorTest.java index 3d893ac1865..874adb4d5fa 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/processor/ClientManageProcessorTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/ClientManageProcessorTest.java @@ -18,22 +18,31 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; -import java.util.HashMap; +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.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.UnregisterClientRequestHeader; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData; +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; @@ -42,7 +51,6 @@ 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.when; @@ -68,7 +76,7 @@ public void init() { clientChannelInfo = new ClientChannelInfo(channel, clientId, LanguageCode.JAVA, 100); brokerController.getProducerManager().registerProducer(group, clientChannelInfo); - ConsumerData consumerData = createConsumerData(group, topic); + ConsumerData consumerData = PullMessageProcessorTest.createConsumerData(group, topic); brokerController.getConsumerManager().registerConsumer( consumerData.getGroupName(), clientChannelInfo, @@ -109,6 +117,48 @@ public void processRequest_UnRegisterConsumer() throws RemotingCommandException 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); @@ -130,4 +180,50 @@ private RemotingCommand createUnRegisterConsumerCommand() { request.makeCustomHeaderToNet(); return request; } -} \ No newline at end of file + + 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; + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/ConsumerManageProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/ConsumerManageProcessorTest.java new file mode 100644 index 00000000000..dd7584b5276 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/ConsumerManageProcessorTest.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.topic.TopicConfigManager; +import org.apache.rocketmq.common.BrokerConfig; +import org.apache.rocketmq.common.TopicConfig; +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.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.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 static org.assertj.core.api.Assertions.assertThat; +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; + + private String topic = "FooBar"; + private String group = "FooBarGroup"; + + @Before + public void init() { + brokerController.setMessageStore(messageStore); + TopicConfigManager topicConfigManager = new TopicConfigManager(brokerController); + topicConfigManager.getTopicConfigTable().put(topic, new TopicConfig(topic)); + when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager); + consumerManageProcessor = new ConsumerManageProcessor(brokerController); + } + + @Test + public void testUpdateConsumerOffset_InvalidTopic() throws Exception { + RemotingCommand request = createConsumerManageCommand(RequestCode.UPDATE_CONSUMER_OFFSET); + request.addExtField("topic", "InvalidTopic"); + RemotingCommand response = consumerManageProcessor.processRequest(handlerContext, request); + assertThat(response).isNotNull(); + assertThat(response.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST); + } + + private RemotingCommand createConsumerManageCommand(int requestCode) { + 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); + + RemotingCommand request = RemotingCommand.createRequestCommand(requestCode, requestHeader); + request.setBody(new byte[] {'a'}); + request.makeCustomHeaderToNet(); + return request; + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/EndTransactionProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/EndTransactionProcessorTest.java index 019cc6a316c..72b339ae73a 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/processor/EndTransactionProcessorTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/EndTransactionProcessorTest.java @@ -24,17 +24,17 @@ 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.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader; +import org.apache.rocketmq.common.message.MessageExtBrokerInner; 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.MessageExtBrokerInner; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; @@ -46,6 +46,8 @@ 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; @@ -61,7 +63,7 @@ public class EndTransactionProcessorTest { @Spy private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), - new MessageStoreConfig()); + new MessageStoreConfig()); @Mock private MessageStore messageStore; @@ -76,7 +78,7 @@ public void init() { endTransactionProcessor = new EndTransactionProcessor(brokerController); } - private OperationResult createResponse(int status){ + private OperationResult createResponse(int status) { OperationResult response = new OperationResult(); response.setPrepareMessage(createDefaultMessageExt()); response.setResponseCode(status); @@ -87,8 +89,8 @@ private OperationResult createResponse(int status){ @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, new AppendMessageResult(AppendMessageStatus.PUT_OK))); + when(messageStore.putMessage(any(MessageExtBrokerInner.class))) + .thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); RemotingCommand request = createEndTransactionMsgCommand(MessageSysFlag.TRANSACTION_COMMIT_TYPE, false); RemotingCommand response = endTransactionProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); @@ -97,8 +99,8 @@ public void testProcessRequest() throws RemotingCommandException { @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, new AppendMessageResult(AppendMessageStatus.PUT_OK))); + when(messageStore.putMessage(any(MessageExtBrokerInner.class))) + .thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); RemotingCommand request = createEndTransactionMsgCommand(MessageSysFlag.TRANSACTION_COMMIT_TYPE, true); RemotingCommand response = endTransactionProcessor.processRequest(handlerContext, request); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); @@ -119,6 +121,22 @@ public void testProcessRequest_RollBack() throws RemotingCommandException { 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"); @@ -149,4 +167,27 @@ private RemotingCommand createEndTransactionMsgCommand(int status, boolean isChe 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; + } } diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/PopBufferMergeServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopBufferMergeServiceTest.java new file mode 100644 index 00000000000..acc7a3da74a --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopBufferMergeServiceTest.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.commons.lang3.reflect.FieldUtils; +import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.broker.client.ClientChannelInfo; +import org.apache.rocketmq.broker.schedule.ScheduleMessageService; +import org.apache.rocketmq.common.BrokerConfig; +import org.apache.rocketmq.common.MixAll; +import org.apache.rocketmq.common.TopicConfig; +import org.apache.rocketmq.remoting.netty.NettyClientConfig; +import org.apache.rocketmq.remoting.netty.NettyServerConfig; +import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData; +import org.apache.rocketmq.store.DefaultMessageStore; +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.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.mock; + +@RunWith(MockitoJUnitRunner.Silent.class) +public class PopBufferMergeServiceTest { + @Spy + private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); + @Mock + private PopMessageProcessor popMessageProcessor; + @Mock + private ChannelHandlerContext handlerContext; + @Mock + private DefaultMessageStore messageStore; + private ScheduleMessageService scheduleMessageService; + private ClientChannelInfo clientChannelInfo; + private String group = "FooBarGroup"; + private String topic = "FooBar"; + + @Before + public void init() throws Exception { + FieldUtils.writeField(brokerController.getBrokerConfig(), "enablePopBufferMerge", true, true); + brokerController.setMessageStore(messageStore); + popMessageProcessor = new PopMessageProcessor(brokerController); + scheduleMessageService = new ScheduleMessageService(brokerController); + scheduleMessageService.parseDelayLevel(); + Channel mockChannel = mock(Channel.class); + brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig()); + clientChannelInfo = new ClientChannelInfo(mockChannel); + ConsumerData consumerData = createConsumerData(group, topic); + brokerController.getConsumerManager().registerConsumer( + consumerData.getGroupName(), + clientChannelInfo, + consumerData.getConsumeType(), + consumerData.getMessageModel(), + consumerData.getConsumeFromWhere(), + consumerData.getSubscriptionDataSet(), + false); + } + + @Test(timeout = 10_000) + public void testBasic() throws Exception { + // This test case fails on Windows in CI pipeline + // Disable it for later fix + Assume.assumeFalse(MixAll.isWindows()); + PopBufferMergeService popBufferMergeService = new PopBufferMergeService(brokerController, popMessageProcessor); + popBufferMergeService.start(); + 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(group); + ck.setTopic(topic); + 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(group); + ackMsg.setTopic(topic); + ackMsg.setQueueId(queueId); + ackMsg.setPopTime(popTime); + try { + assertThat(popBufferMergeService.addCk(ck, reviveQid, ackOffset, nextBeginOffset)).isTrue(); + assertThat(popBufferMergeService.getLatestOffset(topic, group, queueId)).isEqualTo(nextBeginOffset); + Thread.sleep(1000); // wait background threads of PopBufferMergeService run for some time + assertThat(popBufferMergeService.addAk(reviveQid, ackMsg)).isTrue(); + assertThat(popBufferMergeService.getLatestOffset(topic, group, queueId)).isEqualTo(nextBeginOffset); + } finally { + popBufferMergeService.shutdown(true); + } + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/PopInflightMessageCounterTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopInflightMessageCounterTest.java new file mode 100644 index 00000000000..dea59fc99e6 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopInflightMessageCounterTest.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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)); + } +} \ No newline at end of file diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/PopMessageProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopMessageProcessorTest.java new file mode 100644 index 00000000000..44f04066ca8 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopMessageProcessorTest.java @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.broker.client.ClientChannelInfo; +import org.apache.rocketmq.common.BrokerConfig; +import org.apache.rocketmq.common.TopicConfig; +import org.apache.rocketmq.common.constant.ConsumeInitMode; +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.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.logfile.DefaultMappedFile; +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.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 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); + popMessageProcessor = new PopMessageProcessor(brokerController); + when(handlerContext.channel()).thenReturn(embeddedChannel); + 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 { + 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"); + } + + private RemotingCommand createPopMsgCommand() { + PopMessageRequestHeader requestHeader = new PopMessageRequestHeader(); + requestHeader.setConsumerGroup(group); + requestHeader.setMaxMsgNums(30); + requestHeader.setQueueId(-1); + requestHeader.setTopic(topic); + requestHeader.setInvisibleTime(10_000); + requestHeader.setInitMode(ConsumeInitMode.MAX); + 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; + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/PopReviveServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopReviveServiceTest.java new file mode 100644 index 00000000000..1c3a0cd459a --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopReviveServiceTest.java @@ -0,0 +1,284 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.fastjson.JSON; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.apache.rocketmq.broker.BrokerController; +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.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.MessageStore; +import org.apache.rocketmq.store.pop.AckMsg; +import org.apache.rocketmq.store.pop.PopCheckPoint; +import org.apache.rocketmq.store.timer.TimerMessageStore; +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.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.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.Silent.class) +public class PopReviveServiceTest { + + private static final String REVIVE_TOPIC = PopAckConstants.REVIVE_TOPIC + "test"; + 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"); + + @Mock + private MessageStore messageStore; + @Mock + private ConsumerOffsetManager consumerOffsetManager; + @Mock + private TopicConfigManager topicConfigManager; + @Mock + private TimerMessageStore timerMessageStore; + @Mock + private SubscriptionGroupManager subscriptionGroupManager; + @Mock + private BrokerController brokerController; + + private BrokerConfig brokerConfig; + private PopReviveService popReviveService; + + @Before + public void before() { + brokerConfig = new BrokerConfig(); + + 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(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()); + + 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()); + } + + 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(1000); + 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); + + return ackMsg; + } + + public static MessageExtBrokerInner buildCkMsg(PopCheckPoint ck) { + MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); + + msgInner.setTopic(REVIVE_TOPIC); + msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.charset)); + 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)); + 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; + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/PullMessageProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/PullMessageProcessorTest.java index c96f708e854..83c30111854 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/processor/PullMessageProcessorTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/PullMessageProcessorTest.java @@ -16,36 +16,40 @@ */ package org.apache.rocketmq.broker.processor; -import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; -import java.net.InetSocketAddress; +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.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.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.PullMessageRequestHeader; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; 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; @@ -58,16 +62,17 @@ 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 PullMessageProcessorTest { private PullMessageProcessor pullMessageProcessor; @Spy - private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); + 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; @@ -77,12 +82,13 @@ public class PullMessageProcessorTest { @Before public void init() { brokerController.setMessageStore(messageStore); + SubscriptionGroupManager subscriptionGroupManager = new SubscriptionGroupManager(brokerController); pullMessageProcessor = new PullMessageProcessor(brokerController); - Channel mockChannel = mock(Channel.class); - when(mockChannel.remoteAddress()).thenReturn(new InetSocketAddress(1024)); - when(handlerContext.channel()).thenReturn(mockChannel); + 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(mockChannel); + clientChannelInfo = new ClientChannelInfo(embeddedChannel); ConsumerData consumerData = createConsumerData(group, topic); brokerController.getConsumerManager().registerConsumer( consumerData.getGroupName(), @@ -127,10 +133,11 @@ public void testProcessRequest_SubNotLatest() throws RemotingCommandException { @Test public void testProcessRequest_Found() throws RemotingCommandException { GetMessageResult getMessageResult = createGetMessageResult(); - when(messageStore.getMessage(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any(ExpressionMessageFilter.class))).thenReturn(getMessageResult); + when(messageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any(ExpressionMessageFilter.class))).thenReturn(CompletableFuture.completedFuture(getMessageResult)); final RemotingCommand request = createPullMsgCommand(RequestCode.PULL_MESSAGE); - RemotingCommand response = pullMessageProcessor.processRequest(handlerContext, request); + pullMessageProcessor.processRequest(handlerContext, request); + RemotingCommand response = embeddedChannel.readOutbound(); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); } @@ -138,7 +145,7 @@ public void testProcessRequest_Found() throws RemotingCommandException { @Test public void testProcessRequest_FoundWithHook() throws RemotingCommandException { GetMessageResult getMessageResult = createGetMessageResult(); - when(messageStore.getMessage(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any(ExpressionMessageFilter.class))).thenReturn(getMessageResult); + 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() { @@ -159,7 +166,8 @@ public void consumeMessageAfter(ConsumeMessageContext context) { consumeMessageHookList.add(consumeMessageHook); pullMessageProcessor.registerConsumeMessageHook(consumeMessageHookList); final RemotingCommand request = createPullMsgCommand(RequestCode.PULL_MESSAGE); - RemotingCommand response = pullMessageProcessor.processRequest(handlerContext, request); + pullMessageProcessor.processRequest(handlerContext, request); + RemotingCommand response = embeddedChannel.readOutbound(); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS); assertThat(messageContext[0]).isNotNull(); @@ -172,10 +180,11 @@ public void consumeMessageAfter(ConsumeMessageContext context) { public void testProcessRequest_MsgWasRemoving() throws RemotingCommandException { GetMessageResult getMessageResult = createGetMessageResult(); getMessageResult.setStatus(GetMessageStatus.MESSAGE_WAS_REMOVING); - when(messageStore.getMessage(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any(ExpressionMessageFilter.class))).thenReturn(getMessageResult); + when(messageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any(ExpressionMessageFilter.class))).thenReturn(CompletableFuture.completedFuture(getMessageResult)); final RemotingCommand request = createPullMsgCommand(RequestCode.PULL_MESSAGE); - RemotingCommand response = pullMessageProcessor.processRequest(handlerContext, request); + pullMessageProcessor.processRequest(handlerContext, request); + RemotingCommand response = embeddedChannel.readOutbound(); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(ResponseCode.PULL_RETRY_IMMEDIATELY); } @@ -184,14 +193,57 @@ public void testProcessRequest_MsgWasRemoving() throws RemotingCommandException public void testProcessRequest_NoMsgInQueue() throws RemotingCommandException { GetMessageResult getMessageResult = createGetMessageResult(); getMessageResult.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE); - when(messageStore.getMessage(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any(ExpressionMessageFilter.class))).thenReturn(getMessageResult); + when(messageStore.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any(ExpressionMessageFilter.class))).thenReturn(CompletableFuture.completedFuture(getMessageResult)); final RemotingCommand request = createPullMsgCommand(RequestCode.PULL_MESSAGE); - RemotingCommand response = pullMessageProcessor.processRequest(handlerContext, request); + 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); @@ -232,4 +284,4 @@ private GetMessageResult createGetMessageResult() { getMessageResult.setNextBeginOffset(516); return getMessageResult; } -} \ No newline at end of file +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessorTest.java new file mode 100644 index 00000000000..e91c1a09617 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessorTest.java @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.util.ArrayList; +import java.util.List; +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 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; + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessorTest.java index eeca6e7622c..266c8491cbf 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessorTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessorTest.java @@ -28,10 +28,7 @@ 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.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageResponseHeader; +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; @@ -40,9 +37,12 @@ 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.MessageExtBrokerInner; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; @@ -96,7 +96,7 @@ public void testProcessRequest_Success() throws RemotingCommandException, Interr 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(Channel.class), any(RemotingCommand.class))).thenReturn(createResponse(ResponseCode.SUCCESS, request)); + 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()); @@ -121,7 +121,7 @@ private SendMessageRequestHeader createSendMessageRequestHeader() { requestHeader.setBornTimestamp(System.currentTimeMillis()); requestHeader.setFlag(124); requestHeader.setReconsumeTimes(0); - Map map = new HashMap(); + Map map = new HashMap<>(); map.put(MessageConst.PROPERTY_MESSAGE_REPLY_TO_CLIENT, "127.0.0.1"); requestHeader.setProperties(MessageDecoder.messageProperties2String(map)); return requestHeader; @@ -133,4 +133,4 @@ private RemotingCommand createResponse(int code, RemotingCommand request) { response.setOpaque(request.getOpaque()); return response; } -} \ No newline at end of file +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/SendMessageProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/SendMessageProcessorTest.java index 1f81bdb8cc8..e046c888438 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/processor/SendMessageProcessorTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/SendMessageProcessorTest.java @@ -18,27 +18,40 @@ 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.rocketmq.broker.BrokerController; +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.MessageConst; import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.common.message.MessageExt; -import org.apache.rocketmq.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader; +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.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.store.AppendMessageResult; import org.apache.rocketmq.store.AppendMessageStatus; -import org.apache.rocketmq.store.MessageExtBrokerInner; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; @@ -48,21 +61,13 @@ 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.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; 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.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) @@ -70,8 +75,11 @@ public class SendMessageProcessorTest { private SendMessageProcessor sendMessageProcessor; @Mock private ChannelHandlerContext handlerContext; + @Mock + private Channel channel; @Spy - private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig()); + private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), + new NettyClientConfig(), new MessageStoreConfig()); @Mock private MessageStore messageStore; @@ -84,27 +92,30 @@ public class SendMessageProcessorTest { @Before public void init() { brokerController.setMessageStore(messageStore); + 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(messageStore.now()).thenReturn(System.currentTimeMillis()); - Channel mockChannel = mock(Channel.class); - when(mockChannel.remoteAddress()).thenReturn(new InetSocketAddress(1024)); - when(handlerContext.channel()).thenReturn(mockChannel); - MessageExt messageExt = new MessageExt(); - messageExt.setTopic(topic); - when(messageStore.lookMessageByOffset(anyLong())).thenReturn(messageExt); + 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 RemotingCommandException { - when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))) - .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); + 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 RemotingCommandException { - when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))) - .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); + 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() { @@ -126,72 +137,71 @@ public void sendMessageAfter(SendMessageContext context) { sendMessageHookList.add(sendMessageHook); sendMessageProcessor.registerSendMessageHook(sendMessageHookList); assertPutResult(ResponseCode.SUCCESS); - System.out.println(sendMessageContext[0]); assertThat(sendMessageContext[0]).isNotNull(); assertThat(sendMessageContext[0].getTopic()).isEqualTo(topic); assertThat(sendMessageContext[0].getProducerGroup()).isEqualTo(group); } @Test - public void testProcessRequest_FlushTimeOut() throws RemotingCommandException { - when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))) - .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.FLUSH_DISK_TIMEOUT, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); + 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 RemotingCommandException { - when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))) - .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); + 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 RemotingCommandException { - when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))) - .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); + 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 RemotingCommandException { - when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))) - .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.FLUSH_SLAVE_TIMEOUT, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); + 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 RemotingCommandException { - when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))) - .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.OS_PAGECACHE_BUSY, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); + 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_ERROR); } @Test - public void testProcessRequest_PropertiesTooLong() throws RemotingCommandException { - when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))) - .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PROPERTIES_SIZE_EXCEEDED, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); + 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 RemotingCommandException { - when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))) - .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); + 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 RemotingCommandException { - when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))) - .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.SLAVE_NOT_AVAILABLE, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)))); + 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 RemotingCommandException { - when(messageStore.asyncPutMessage(any(MessageExtBrokerInner.class))) - .thenReturn(CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)))); + 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); @@ -204,24 +214,91 @@ public void testProcessRequest_WithMsgBack() throws RemotingCommandException { 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)))); + .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(new Answer() { + 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 Object answer(InvocationOnMock invocation) throws Throwable { - response[0] = invocation.getArgument(0); + public String hookName() { return null; } - }).when(handlerContext).writeAndFlush(any(Object.class)); - RemotingCommand responseToReturn = sendMessageProcessor.processRequest(handlerContext, request); - if (responseToReturn != null) { - assertThat(response[0]).isNull(); - response[0] = responseToReturn; - } - assertThat(response[0].getCode()).isEqualTo(ResponseCode.SUCCESS); + @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); } + private RemotingCommand createSendTransactionMsgCommand(int requestCode) { SendMessageRequestHeader header = createSendMsgRequestHeader(); int sysFlag = header.getSysFlag(); @@ -272,22 +349,34 @@ private RemotingCommand createSendMsgBackCommand(int requestCode) { 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(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - response[0] = invocation.getArgument(0); - return null; + 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; } - }).when(handlerContext).writeAndFlush(any(Object.class)); - RemotingCommand responseToReturn = sendMessageProcessor.processRequest(handlerContext, request); - if (responseToReturn != null) { - assertThat(response[0]).isNull(); - response[0] = responseToReturn; - } - assertThat(response[0].getCode()).isEqualTo(responseCode); - assertThat(response[0].getOpaque()).isEqualTo(request.getOpaque()); + assertThat(response[0].getCode()).isEqualTo(responseCode); + assertThat(response[0].getOpaque()).isEqualTo(request.getOpaque()); + return true; + }); } -} \ No newline at end of file +} diff --git a/store/src/test/java/org/apache/rocketmq/store/schedule/ScheduleMessageServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/schedule/ScheduleMessageServiceTest.java similarity index 55% rename from store/src/test/java/org/apache/rocketmq/store/schedule/ScheduleMessageServiceTest.java rename to broker/src/test/java/org/apache/rocketmq/broker/schedule/ScheduleMessageServiceTest.java index de3cf7f5945..b90fb2931d5 100644 --- a/store/src/test/java/org/apache/rocketmq/store/schedule/ScheduleMessageServiceTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/schedule/ScheduleMessageServiceTest.java @@ -15,20 +15,10 @@ * limitations under the License. */ -package org.apache.rocketmq.store.schedule; - -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.store.*; -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; +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; @@ -37,44 +27,68 @@ 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.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.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.BROKER_PUT_NUMS; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.TOPIC_PUT_NUMS; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.TOPIC_PUT_SIZE; +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" + * 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 = 2; + int delayLevel = 3; - private static final String storePath = System.getProperty("user.home") + File.separator + "schedule_test#" + UUID.randomUUID(); - private static final int commitLogFileSize = 1024; - private static final int cqFileSize = 10; - private static final int cqExtFileSize = 10 * (ConsumeQueueExt.CqExtUnit.MIN_EXT_UNIT_SIZE + 64); + 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; - DefaultMessageStore messageStore; - MessageStoreConfig messageStoreConfig; - BrokerConfig brokerConfig; - ScheduleMessageService scheduleMessageService; + 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 { @@ -89,32 +103,97 @@ public class ScheduleMessageServiceTest { } } - @Before - public void init() throws Exception { + public void setUp() throws Exception { messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMessageDelayLevel(testMessageDelayLevel); - messageStoreConfig.setMappedFileSizeCommitLog(commitLogFileSize); - messageStoreConfig.setMappedFileSizeConsumeQueue(cqFileSize); - messageStoreConfig.setMappedFileSizeConsumeQueueExt(cqExtFileSize); + 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(storePath); - messageStoreConfig.setStorePathCommitLog(storePath + File.separator + "commitlog"); + 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()); + messageStore = new DefaultMessageStore(messageStoreConfig, manager, new MyMessageArrivingListener(), new BrokerConfig(), new ConcurrentHashMap<>()); assertThat(messageStore.load()).isTrue(); messageStore.start(); - scheduleMessageService = messageStore.getScheduleMessageService(); + 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); + scheduleMessageService = new ScheduleMessageService(brokerController); + scheduleMessageService.load(); + scheduleMessageService.start(); + Mockito.when(brokerController.getScheduleMessageService()).thenReturn(scheduleMessageService); + } + + @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 deliverDelayedMessageTimerTaskTest() throws Exception { + public void testDeliverDelayedMessageTimerTask() throws Exception { assertThat(messageStore.getMessageStoreConfig().isEnableScheduleMessageStats()).isTrue(); assertThat(messageStore.getBrokerStatsManager().getStatsItem(TOPIC_PUT_NUMS, topic)).isNull(); @@ -123,12 +202,10 @@ public void deliverDelayedMessageTimerTaskTest() throws Exception { 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(); - // make sure consumerQueue offset = commitLog offset - StoreTestUtil.waitCommitLogReput(messageStore); - // consumer message int delayQueueId = ScheduleMessageService.delayLevel2QueueId(delayLevel); assertThat(delayQueueId).isEqualTo(delayLevel - 1); @@ -141,8 +218,8 @@ public void deliverDelayedMessageTimerTaskTest() throws Exception { // timer run maybe delay, then consumer message again // and wait offsetTable - TimeUnit.SECONDS.sleep(10); - scheduleMessageService.buildRunningStats(new HashMap()); + TimeUnit.SECONDS.sleep(15); + scheduleMessageService.buildRunningStats(new HashMap<>()); messageResult = getMessage(realQueueId, offset); // now,found the message @@ -191,23 +268,21 @@ public void otherTest() { scheduleMessageService.decode(new DelayOffsetSerializeWrapper().toJson()); } - private GetMessageResult getMessage(int queueId, Long offset) { return messageStore.getMessage(messageGroup, topic, - queueId, offset, 1, null); + 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(); @@ -223,13 +298,10 @@ public MessageExtBrokerInner buildMessage() { 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) { + byte[] filterBitMap, Map properties) { } } - - -} +} \ No newline at end of file diff --git a/broker/src/test/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManagerTest.java new file mode 100644 index 00000000000..6337c69ea7b --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManagerTest.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.util.Map; +import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.common.SubscriptionGroupAttributes; +import org.apache.rocketmq.common.attribute.BooleanAttribute; +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.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class SubscriptionGroupManagerTest { + private String group = "group"; + @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)); + when(brokerControllerMock.getMessageStore()).thenReturn(null); + doNothing().when(subscriptionGroupManager).persist(); + } + + @Test + public void updateSubscriptionGroupConfig() { + SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); + subscriptionGroupConfig.setGroupName(group); + Map attr = ImmutableMap.of("+test", "true"); + subscriptionGroupConfig.setAttributes(attr); + 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"); + } +} \ No newline at end of file diff --git a/broker/src/test/java/org/apache/rocketmq/broker/substription/ForbiddenTest.java b/broker/src/test/java/org/apache/rocketmq/broker/substription/ForbiddenTest.java new file mode 100644 index 00000000000..2ac5ee320d3 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/substription/ForbiddenTest.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.broker.substription; + +import static org.junit.Assert.assertEquals; + +import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager; +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)); + } + +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/topic/TopicConfigManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/topic/TopicConfigManagerTest.java new file mode 100644 index 00000000000..6052a79d413 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/topic/TopicConfigManagerTest.java @@ -0,0 +1,321 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.List; +import java.util.Map; +import java.util.Optional; +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.utils.QueueTypeUtils; +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.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 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(); + when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig); + 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); + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/topic/TopicQueueMappingManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/topic/TopicQueueMappingManagerTest.java new file mode 100644 index 00000000000..b74e57ab936 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/topic/TopicQueueMappingManagerTest.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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 org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.common.BrokerConfig; +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 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<>()); + Assert.assertEquals(1, topicRemappingDetailWrapper.getBrokerConfigMap().size()); + TopicQueueMappingDetail topicQueueMappingDetail = topicRemappingDetailWrapper.getBrokerConfigMap().values().iterator().next().getMappingDetail(); + Assert.assertEquals(queueNum, topicQueueMappingDetail.getHostedQueues().size()); + mappingDetailMap.put(topic, topicQueueMappingDetail); + } + } + + { + topicQueueMappingManager = new TopicQueueMappingManager(brokerController); + Assert.assertTrue(topicQueueMappingManager.load()); + Assert.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()); + Assert.assertEquals(mappingDetailMap.size(), topicQueueMappingManager.getTopicQueueMappingTable().size()); + for (TopicQueueMappingDetail topicQueueMappingDetail: topicQueueMappingManager.getTopicQueueMappingTable().values()) { + Assert.assertEquals(topicQueueMappingDetail, mappingDetailMap.get(topicQueueMappingDetail.getTopic())); + } + } + delete(topicQueueMappingManager); + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListenerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListenerTest.java index f6035463c01..986b15aa098 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListenerTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListenerTest.java @@ -22,10 +22,10 @@ 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.MessageExtBrokerInner; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; diff --git a/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageBridgeTest.java b/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageBridgeTest.java index 031a55a5d9d..e01182fcbbe 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageBridgeTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageBridgeTest.java @@ -16,6 +16,9 @@ */ 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; @@ -23,6 +26,7 @@ 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; @@ -31,7 +35,6 @@ import org.apache.rocketmq.store.AppendMessageStatus; import org.apache.rocketmq.store.GetMessageResult; import org.apache.rocketmq.store.GetMessageStatus; -import org.apache.rocketmq.store.MessageExtBrokerInner; import org.apache.rocketmq.store.MessageFilter; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; @@ -45,10 +48,6 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -76,14 +75,16 @@ public void init() { @Test public void testPutOpMessage() { - boolean isSuccess = transactionBridge.putOpMessage(createMessageBrokerInner(), TransactionalMessageUtil.REMOVETAG); + 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))); + 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); } @@ -133,16 +134,16 @@ public void testGetOpMessage() { @Test public void testPutMessageReturnResult() { - when(messageStore.putMessage(any(MessageExtBrokerInner.class))).thenReturn(new PutMessageResult - (PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); + 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))); + 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); } @@ -166,11 +167,11 @@ public void testRenewHalfMessageInner() { MessageExt messageExt = new MessageExt(); long bornTimeStamp = messageExt.getBornTimestamp(); MessageExt messageExtRes = transactionBridge.renewHalfMessageInner(messageExt); - assertThat( messageExtRes.getBornTimestamp()).isEqualTo(bornTimeStamp); + assertThat(messageExtRes.getBornTimestamp()).isEqualTo(bornTimeStamp); } @Test - public void testLookMessageByOffset(){ + public void testLookMessageByOffset() { when(messageStore.lookMessageByOffset(anyLong())).thenReturn(new MessageExt()); MessageExt messageExt = transactionBridge.lookMessageByOffset(123); assertThat(messageExt).isNotNull(); diff --git a/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImplTest.java b/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImplTest.java index 8b138fc896a..2a63774555e 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImplTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImplTest.java @@ -16,6 +16,11 @@ */ 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; @@ -23,18 +28,19 @@ 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.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader; 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.MessageExtBrokerInner; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.config.MessageStoreConfig; @@ -47,18 +53,13 @@ import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; - 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.doAnswer; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) @@ -78,16 +79,16 @@ public class TransactionalMessageServiceImplTest { @Before public void init() { + when(bridge.getBrokerController()).thenReturn(brokerController); listener.setBrokerController(brokerController); queueTransactionMsgService = new TransactionalMessageServiceImpl(bridge); - brokerController.getMessageStoreConfig().setFileReservedTime(3); } @Test public void testPrepareMessage() { MessageExtBrokerInner inner = createMessageBrokerInner(); - when(bridge.putHalfMessage(any(MessageExtBrokerInner.class))).thenReturn(new PutMessageResult - (PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK))); + 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(); } @@ -112,6 +113,7 @@ public void testCheck_withDiscard() { 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); @@ -134,8 +136,8 @@ public void testCheck_withCheck() { 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))); + 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); @@ -151,10 +153,24 @@ public Object answer(InvocationOnMock invocation) { } @Test - public void testDeletePrepareMessage() { - when(bridge.putOpMessage(any(MessageExt.class), anyString())).thenReturn(true); + 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 @@ -189,7 +205,7 @@ private PullResult createOpPulResult(String topic, long queueOffset, String body PullResult result = createPullResult(topic, queueOffset, body, size); List msgs = result.getMsgFoundList(); for (MessageExt msg : msgs) { - msg.setTags(TransactionalMessageUtil.REMOVETAG); + msg.setTags(TransactionalMessageUtil.REMOVE_TAG); } return result; } diff --git a/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageUtilTest.java b/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageUtilTest.java new file mode 100644 index 00000000000..722a306848e --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageUtilTest.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); + } +} \ No newline at end of file diff --git a/broker/src/test/java/org/apache/rocketmq/broker/util/HookUtilsTest.java b/broker/src/test/java/org/apache/rocketmq/broker/util/HookUtilsTest.java new file mode 100644 index 00000000000..738690c691b --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/util/HookUtilsTest.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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()); + } +} \ No newline at end of file diff --git a/broker/src/test/java/org/apache/rocketmq/broker/util/ServiceProviderTest.java b/broker/src/test/java/org/apache/rocketmq/broker/util/ServiceProviderTest.java index a3a35c8832d..53fba00fa9e 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/util/ServiceProviderTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/util/ServiceProviderTest.java @@ -20,6 +20,7 @@ import org.apache.rocketmq.acl.AccessValidator; 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; @@ -30,21 +31,20 @@ public class ServiceProviderTest { @Test public void loadTransactionMsgServiceTest() { - TransactionalMessageService transactionService = ServiceProvider.loadClass(ServiceProvider.TRANSACTION_SERVICE_ID, - TransactionalMessageService.class); + TransactionalMessageService transactionService = ServiceProvider.loadClass(TransactionalMessageService.class); assertThat(transactionService).isNotNull(); } @Test public void loadAbstractTransactionListenerTest() { - AbstractTransactionalMessageCheckListener listener = ServiceProvider.loadClass(ServiceProvider.TRANSACTION_LISTENER_ID, - AbstractTransactionalMessageCheckListener.class); + AbstractTransactionalMessageCheckListener listener = ServiceProvider.loadClass( + AbstractTransactionalMessageCheckListener.class); assertThat(listener).isNotNull(); } @Test public void loadAccessValidatorTest() { - List accessValidators = ServiceProvider.load(ServiceProvider.ACL_VALIDATOR_ID, AccessValidator.class); - assertThat(accessValidators).isNotNull(); + List accessValidators = ServiceProvider.load(AccessValidator.class); + assertThat(accessValidators).isNotNull(); } } diff --git a/broker/src/test/java/org/apache/rocketmq/broker/util/TransactionalMessageServiceImpl.java b/broker/src/test/java/org/apache/rocketmq/broker/util/TransactionalMessageServiceImpl.java index 281bd57bfef..3cbfab2c270 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/util/TransactionalMessageServiceImpl.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/util/TransactionalMessageServiceImpl.java @@ -16,21 +16,20 @@ */ 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.TransactionalMessageService; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageExt; -import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.store.MessageExtBrokerInner; +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; -import java.util.concurrent.CompletableFuture; - public class TransactionalMessageServiceImpl implements TransactionalMessageService { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.TRANSACTION_LOGGER_NAME); @Override public PutMessageResult prepareMessage(MessageExtBrokerInner messageInner) { diff --git a/store/src/test/resources/logback-test.xml b/broker/src/test/resources/rmq.logback-test.xml similarity index 66% rename from store/src/test/resources/logback-test.xml rename to broker/src/test/resources/rmq.logback-test.xml index a033816ddad..8695d52d57c 100644 --- a/store/src/test/resources/logback-test.xml +++ b/broker/src/test/resources/rmq.logback-test.xml @@ -17,19 +17,20 @@ --> - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n - UTF-8 - + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + - - + + - - + + - + \ No newline at end of file diff --git a/client/BUILD.bazel b/client/BUILD.bazel new file mode 100644 index 00000000000..e491cfcef0c --- /dev/null +++ b/client/BUILD.bazel @@ -0,0 +1,66 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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_fastjson", + "@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", + ], +) + +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", + ], + 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 = [ + "src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest", + ], +) diff --git a/client/pom.xml b/client/pom.xml index 51e53194f93..5bd725922c3 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.4-SNAPSHOT + 5.1.3 4.0.0 @@ -28,14 +28,13 @@ rocketmq-client ${project.version} - 1.6 - 1.6 + ${basedir}/.. ${project.groupId} - rocketmq-common + rocketmq-remoting io.netty @@ -50,14 +49,22 @@ io.opentracing opentracing-api - 0.33.0 - provided io.opentracing opentracing-mock - 0.33.0 - test + + + com.google.guava + guava + + + io.github.aliyunmq + rocketmq-slf4j-api + + + io.github.aliyunmq + rocketmq-logback-classic diff --git a/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java b/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java index 4452bbdfa1d..f87450f66c3 100644 --- a/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java +++ b/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java @@ -23,19 +23,24 @@ import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.NamespaceUtil; import org.apache.rocketmq.common.utils.NameServerAddressUtils; -import org.apache.rocketmq.remoting.common.RemotingUtil; +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 HEART_BEAT_V2 = "com.rocketmq.heartbeat.v2"; private String namesrvAddr = NameServerAddressUtils.getNameServerAddresses(); - private String clientIP = RemotingUtil.getLocalAddress(); + private String clientIP = NetworkUtil.getLocalAddress(); private String instanceName = System.getProperty("rocketmq.client.name", "DEFAULT"); private int clientCallbackExecutorThreads = Runtime.getRuntime().availableProcessors(); protected String namespace; @@ -57,14 +62,25 @@ public class ClientConfig { private long pullTimeDelayMillsWhenException = 1000; 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 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; + public String buildMQClientId() { StringBuilder sb = new StringBuilder(); sb.append(this.getClientIP()); @@ -76,6 +92,11 @@ public String buildMQClientId() { sb.append(this.unitName); } + if (enableStreamRequestType) { + sb.append("@"); + sb.append(RequestType.STREAM); + } + return sb.toString(); } @@ -106,7 +127,7 @@ public String withNamespace(String resource) { } public Set withNamespace(Set resourceSet) { - Set resourceWithNamespace = new HashSet(); + Set resourceWithNamespace = new HashSet<>(); for (String resource : resourceSet) { resourceWithNamespace.add(withNamespace(resource)); } @@ -118,7 +139,7 @@ public String withoutNamespace(String resource) { } public Set withoutNamespace(Set resourceSet) { - Set resourceWithoutNamespace = new HashSet(); + Set resourceWithoutNamespace = new HashSet<>(); for (String resource : resourceSet) { resourceWithoutNamespace.add(withoutNamespace(resource)); } @@ -157,9 +178,14 @@ public void resetClientConfig(final ClientConfig cc) { 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; } public ClientConfig cloneClientConfig() { @@ -176,9 +202,14 @@ public ClientConfig cloneClientConfig() { 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; return cc; } @@ -271,6 +302,14 @@ 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; } @@ -279,6 +318,22 @@ 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; + } + public String getNamespace() { if (namespaceInitialized) { return namespace; @@ -318,12 +373,36 @@ public void setMqClientApiTimeout(int mqClientApiTimeout) { this.mqClientApiTimeout = mqClientApiTimeout; } + public boolean isEnableStreamRequestType() { + return enableStreamRequestType; + } + + public void setEnableStreamRequestType(boolean enableStreamRequestType) { + this.enableStreamRequestType = enableStreamRequestType; + } + + public boolean isUseHeartbeatV2() { + return useHeartbeatV2; + } + + public void setUseHeartbeatV2(boolean useHeartbeatV2) { + this.useHeartbeatV2 = useHeartbeatV2; + } + @Override public String toString() { - return "ClientConfig [namesrvAddr=" + namesrvAddr + ", clientIP=" + clientIP + ", instanceName=" + instanceName - + ", clientCallbackExecutorThreads=" + clientCallbackExecutorThreads + ", pollNameServerInterval=" + pollNameServerInterval - + ", heartbeatBrokerInterval=" + heartbeatBrokerInterval + ", persistConsumerOffsetInterval=" + persistConsumerOffsetInterval - + ", pullTimeDelayMillsWhenException=" + pullTimeDelayMillsWhenException + ", unitMode=" + unitMode + ", unitName=" + unitName + ", vipChannelEnabled=" - + vipChannelEnabled + ", useTLS=" + useTLS + ", language=" + language.name() + ", namespace=" + namespace + ", mqClientApiTimeout=" + mqClientApiTimeout + "]"; + return "ClientConfig [namesrvAddr=" + namesrvAddr + + ", clientIP=" + clientIP + ", instanceName=" + instanceName + + ", clientCallbackExecutorThreads=" + clientCallbackExecutorThreads + + ", pollNameServerInterval=" + pollNameServerInterval + + ", heartbeatBrokerInterval=" + heartbeatBrokerInterval + + ", persistConsumerOffsetInterval=" + persistConsumerOffsetInterval + + ", pullTimeDelayMillsWhenException=" + pullTimeDelayMillsWhenException + + ", unitMode=" + unitMode + ", unitName=" + unitName + + ", vipChannelEnabled=" + vipChannelEnabled + ", useTLS=" + useTLS + + ", socksProxyConfig=" + socksProxyConfig + ", language=" + language.name() + + ", namespace=" + namespace + ", mqClientApiTimeout=" + mqClientApiTimeout + + ", decodeReadBody=" + decodeReadBody + ", decodeDecompressBody=" + decodeDecompressBody + + ", enableStreamRequestType=" + enableStreamRequestType + ", useHeartbeatV2=" + useHeartbeatV2 + "]"; } } diff --git a/client/src/main/java/org/apache/rocketmq/client/MQAdmin.java b/client/src/main/java/org/apache/rocketmq/client/MQAdmin.java index 63b2d14531d..c2e936be43f 100644 --- a/client/src/main/java/org/apache/rocketmq/client/MQAdmin.java +++ b/client/src/main/java/org/apache/rocketmq/client/MQAdmin.java @@ -22,29 +22,31 @@ 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 an topic - * - * @param key accesskey + * 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) + void createTopic(final String key, final String newTopic, final int queueNum, Map attributes) throws MQClientException; /** - * Creates an topic - * - * @param key accesskey + * 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) + void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, Map attributes) throws MQClientException; /** diff --git a/client/src/main/java/org/apache/rocketmq/client/MQHelper.java b/client/src/main/java/org/apache/rocketmq/client/MQHelper.java index cfd0b7ee1cb..9da6f5b4e48 100644 --- a/client/src/main/java/org/apache/rocketmq/client/MQHelper.java +++ b/client/src/main/java/org/apache/rocketmq/client/MQHelper.java @@ -19,12 +19,14 @@ import java.util.Set; import java.util.TreeSet; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; -import org.apache.rocketmq.client.log.ClientLogger; -import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; +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, @@ -37,11 +39,11 @@ public static void resetOffsetByTimestamp( /** * Reset consumer topic offset according to time * - * @param messageModel which model - * @param instanceName which instance + * @param messageModel which model + * @param instanceName which instance * @param consumerGroup consumer group - * @param topic topic - * @param timestamp time + * @param topic topic + * @param timestamp time */ public static void resetOffsetByTimestamp( final MessageModel messageModel, @@ -49,7 +51,6 @@ public static void resetOffsetByTimestamp( final String consumerGroup, final String topic, final long timestamp) throws Exception { - final InternalLogger log = ClientLogger.getLog(); DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(consumerGroup); consumer.setInstanceName(instanceName); @@ -60,7 +61,7 @@ public static void resetOffsetByTimestamp( try { mqs = consumer.fetchSubscribeMessageQueues(topic); if (mqs != null && !mqs.isEmpty()) { - TreeSet mqsNew = new TreeSet(mqs); + TreeSet mqsNew = new TreeSet<>(mqs); for (MessageQueue mq : mqsNew) { long offset = consumer.searchOffset(mq, timestamp); if (offset >= 0) { diff --git a/client/src/main/java/org/apache/rocketmq/client/MqClientAdmin.java b/client/src/main/java/org/apache/rocketmq/client/MqClientAdmin.java new file mode 100644 index 00000000000..4eb74c0ca9b --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/MqClientAdmin.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/client/src/main/java/org/apache/rocketmq/client/Validators.java b/client/src/main/java/org/apache/rocketmq/client/Validators.java index 19208c31553..77e4bbd2383 100644 --- a/client/src/main/java/org/apache/rocketmq/client/Validators.java +++ b/client/src/main/java/org/apache/rocketmq/client/Validators.java @@ -17,14 +17,20 @@ package org.apache.rocketmq.client; -import static org.apache.rocketmq.common.topic.TopicValidator.isTopicOrGroupIllegal; - +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.protocol.ResponseCode; +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 @@ -74,6 +80,12 @@ public static void checkMessage(Message msg, DefaultMQProducer defaultMQProducer 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 { @@ -107,4 +119,19 @@ public static void isNotAllowedSendTopic(String topic) throws MQClientException } } + 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 { + // TODO: use MixAll.isPropertyValid() when jdk upgrade to 1.8 + if (brokerConfig.containsKey("brokerPermission") + && !PermName.isValid(brokerConfig.getProperty("brokerPermission"))) { + throw new MQClientException(ResponseCode.NO_PERMISSION, + String.format("brokerPermission value: %s is invalid.", brokerConfig.getProperty("brokerPermission"))); + } + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/common/NameserverAccessConfig.java b/client/src/main/java/org/apache/rocketmq/client/common/NameserverAccessConfig.java new file mode 100644 index 00000000000..2cdae48ee7c --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/common/NameserverAccessConfig.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java b/client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java index 891c17e3ba2..4a3d90135b6 100644 --- a/client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java +++ b/client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java @@ -20,18 +20,17 @@ import java.util.Random; public class ThreadLocalIndex { - private final ThreadLocal threadLocalIndex = new ThreadLocal(); + 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 = Math.abs(random.nextInt()); - this.threadLocalIndex.set(index); + index = random.nextInt(); } - this.threadLocalIndex.set(++index); - return Math.abs(index); + return index & POSITIVE_MASK; } @Override diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/AckCallback.java b/client/src/main/java/org/apache/rocketmq/client/consumer/AckCallback.java new file mode 100644 index 00000000000..99a261c9de1 --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/AckCallback.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/AckResult.java b/client/src/main/java/org/apache/rocketmq/client/consumer/AckResult.java new file mode 100644 index 00000000000..06cb59a293c --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/AckResult.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + "]"; + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/AckStatus.java b/client/src/main/java/org/apache/rocketmq/client/consumer/AckStatus.java new file mode 100644 index 00000000000..b144f8f454c --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/AckStatus.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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, +} diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java b/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java index 74d6f3455f3..5e5bd4daaaa 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java @@ -18,12 +18,13 @@ import java.util.Collection; 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.log.ClientLogger; import org.apache.rocketmq.client.trace.AsyncTraceDispatcher; import org.apache.rocketmq.client.trace.TraceDispatcher; import org.apache.rocketmq.client.trace.hook.ConsumeMessageTraceHookImpl; @@ -32,14 +33,17 @@ 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.common.protocol.NamespaceUtil; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.logging.InternalLogger; 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; + +import static org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData.SUB_ALL; public class DefaultLitePullConsumer extends ClientConfig implements LitePullConsumer { - private final InternalLogger log = ClientLogger.getLog(); + private static final Logger log = LoggerFactory.getLogger(DefaultLitePullConsumer.class); private final DefaultLitePullConsumerImpl defaultLitePullConsumerImpl; @@ -219,6 +223,7 @@ public DefaultLitePullConsumer(final String consumerGroup, RPCHook rpcHook) { public DefaultLitePullConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook) { this.namespace = namespace; this.consumerGroup = consumerGroup; + this.enableStreamRequestType = true; defaultLitePullConsumerImpl = new DefaultLitePullConsumerImpl(this, rpcHook); } @@ -249,6 +254,11 @@ 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); @@ -263,12 +273,16 @@ public void subscribe(String topic, MessageSelector messageSelector) throws MQCl 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()); @@ -310,11 +324,56 @@ public void registerTopicMessageQueueChangeListener(String topic, 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)); @@ -447,10 +506,12 @@ public void setOffsetStore(OffsetStore offsetStore) { this.offsetStore = offsetStore; } + @Override public boolean isUnitMode() { return unitMode; } + @Override public void setUnitMode(boolean isUnitMode) { this.unitMode = isUnitMode; } diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPullConsumer.java b/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPullConsumer.java index 1c3f3da4d6e..e5cd5415534 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPullConsumer.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPullConsumer.java @@ -17,6 +17,7 @@ package org.apache.rocketmq.client.consumer; import java.util.HashSet; +import java.util.Map; import java.util.Set; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.QueryResult; @@ -29,16 +30,14 @@ 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.protocol.NamespaceUtil; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; 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; /** - * @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 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 { @@ -77,7 +76,7 @@ public class DefaultMQPullConsumer extends ClientConfig implements MQPullConsume /** * Topic set you want to register */ - private Set registerTopics = new HashSet(); + private Set registerTopics = new HashSet<>(); /** * Queue allocation algorithm */ @@ -108,6 +107,7 @@ public DefaultMQPullConsumer(final String consumerGroup, RPCHook rpcHook) { public DefaultMQPullConsumer(final String namespace, final String consumerGroup) { this(namespace, consumerGroup, null); } + /** * Constructor specifying namespace, consumer group and RPC hook. * @@ -117,6 +117,7 @@ public DefaultMQPullConsumer(final String namespace, final String consumerGroup) public DefaultMQPullConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook) { this.namespace = namespace; this.consumerGroup = consumerGroup; + this.enableStreamRequestType = true; defaultMQPullConsumerImpl = new DefaultMQPullConsumerImpl(this, rpcHook); } @@ -125,8 +126,9 @@ public DefaultMQPullConsumer(final String namespace, final String consumerGroup, */ @Deprecated @Override - public void createTopic(String key, String newTopic, int queueNum) throws MQClientException { - createTopic(key, withNamespace(newTopic), queueNum, 0); + public void createTopic(String key, String newTopic, int queueNum, + Map attributes) throws MQClientException { + createTopic(key, withNamespace(newTopic), queueNum, 0, null); } /** @@ -134,7 +136,8 @@ public void createTopic(String key, String newTopic, int queueNum) throws MQClie */ @Deprecated @Override - public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag) throws MQClientException { + public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, + Map attributes) throws MQClientException { this.defaultMQPullConsumerImpl.createTopic(key, withNamespace(newTopic), queueNum, topicSysFlag); } @@ -349,6 +352,14 @@ public void pull(MessageQueue mq, String subExpression, long offset, int maxNums 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) @@ -434,10 +445,12 @@ public DefaultMQPullConsumerImpl getDefaultMQPullConsumerImpl() { return defaultMQPullConsumerImpl; } + @Override public boolean isUnitMode() { return unitMode; } + @Override public void setUnitMode(boolean isUnitMode) { this.unitMode = isUnitMode; } @@ -449,4 +462,8 @@ public int getMaxReconsumeTimes() { public void setMaxReconsumeTimes(final int maxReconsumeTimes) { this.maxReconsumeTimes = maxReconsumeTimes; } + + public void persist(MessageQueue mq) { + this.getOffsetStore().persist(queueWithNamespace(mq)); + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumer.java b/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumer.java index 7fb6dc0999b..1afb9113eb4 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumer.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumer.java @@ -29,8 +29,8 @@ 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.log.ClientLogger; import org.apache.rocketmq.client.trace.AsyncTraceDispatcher; import org.apache.rocketmq.client.trace.TraceDispatcher; import org.apache.rocketmq.client.trace.hook.ConsumeMessageTraceHookImpl; @@ -40,11 +40,12 @@ 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.protocol.NamespaceUtil; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.logging.InternalLogger; 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 org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * In most scenarios, this is the mostly recommended class to consume messages. @@ -63,7 +64,7 @@ */ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsumer { - private final InternalLogger log = ClientLogger.getLog(); + private final Logger log = LoggerFactory.getLogger(DefaultMQPushConsumer.class); /** * Internal implementation. Most of the functions herein are delegated to it. @@ -75,7 +76,7 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume * load balance. It's required and needs to be globally unique. *

* - * See here for further discussion. + * See here for further discussion. */ private String consumerGroup; @@ -142,7 +143,7 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume /** * Subscription relationship */ - private Map subscription = new HashMap(); + private Map subscription = new HashMap<>(); /** * Message listener @@ -180,20 +181,26 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume */ 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 of a message only measured by message body, so it's not accurate + * 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 overwrote and calculated based on - * {@code pullThresholdForTopic} if it is't 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 @@ -203,8 +210,8 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume /** * Limit the cached message size on topic level, default value is -1 MiB(Unlimited) *

- * The value of {@code pullThresholdSizeForQueue} will be overwrote and calculated based on - * {@code pullThresholdSizeForTopic} if it is't 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 @@ -226,6 +233,9 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume */ private int pullBatchSize = 32; + + private int pullBatchSizeInBytes = 256 * 1024; + /** * Whether update subscription relationship when every pull */ @@ -237,7 +247,7 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume private boolean unitMode = false; /** - * Max re-consume times. + * Max re-consume times. * In concurrently mode, -1 means 16; * In orderly mode, -1 means Integer.MAX_VALUE. * @@ -255,6 +265,16 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume */ 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. */ @@ -265,6 +285,9 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume */ private TraceDispatcher traceDispatcher = null; + // force to use client rebalance + private boolean clientRebalance = true; + /** * Default constructor. */ @@ -395,10 +418,9 @@ public DefaultMQPushConsumer(final String namespace, final String consumerGroup, if (enableMsgTrace) { try { AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(consumerGroup, TraceDispatcher.Type.CONSUME, customizedTraceTopic, rpcHook); - dispatcher.setHostConsumer(this.getDefaultMQPushConsumerImpl()); + dispatcher.setHostConsumer(this.defaultMQPushConsumerImpl); traceDispatcher = dispatcher; - this.getDefaultMQPushConsumerImpl().registerConsumeMessageHook( - new ConsumeMessageTraceHookImpl(traceDispatcher)); + this.defaultMQPushConsumerImpl.registerConsumeMessageHook(new ConsumeMessageTraceHookImpl(traceDispatcher)); } catch (Throwable e) { log.error("system mqtrace hook init failed ,maybe can't send msg trace data"); } @@ -410,24 +432,24 @@ public DefaultMQPushConsumer(final String namespace, final String consumerGroup, */ @Deprecated @Override - public void createTopic(String key, String newTopic, int queueNum) throws MQClientException { - createTopic(key, withNamespace(newTopic), queueNum, 0); + 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); - if (traceDispatcher != null && traceDispatcher instanceof AsyncTraceDispatcher) { + if (traceDispatcher instanceof AsyncTraceDispatcher) { ((AsyncTraceDispatcher) traceDispatcher).getTraceProducer().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) throws MQClientException { + public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, Map attributes) throws MQClientException { this.defaultMQPushConsumerImpl.createTopic(key, withNamespace(newTopic), queueNum, topicSysFlag); } @@ -607,6 +629,14 @@ 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; } @@ -640,7 +670,7 @@ public Map getSubscription() { */ @Deprecated public void setSubscription(Map subscription) { - Map subscriptionWithNamespace = new HashMap(); + Map subscriptionWithNamespace = new HashMap<>(subscription.size(), 1); for (Entry topicEntry : subscription.entrySet()) { subscriptionWithNamespace.put(withNamespace(topicEntry.getKey()), topicEntry.getValue()); } @@ -665,7 +695,7 @@ public void setSubscription(Map subscription) { public void sendMessageBack(MessageExt msg, int delayLevel) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { msg.setTopic(withNamespace(msg.getTopic())); - this.defaultMQPushConsumerImpl.sendMessageBack(msg, delayLevel, null); + this.defaultMQPushConsumerImpl.sendMessageBack(msg, delayLevel, (String) null); } /** @@ -828,6 +858,18 @@ 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. */ @@ -860,10 +902,12 @@ public void setPostSubscriptionWhenPull(boolean postSubscriptionWhenPull) { this.postSubscriptionWhenPull = postSubscriptionWhenPull; } + @Override public boolean isUnitMode() { return unitMode; } + @Override public void setUnitMode(boolean isUnitMode) { this.unitMode = isUnitMode; } @@ -900,6 +944,14 @@ 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; } @@ -908,7 +960,31 @@ public void setAwaitTerminationMillisWhenShutdown(long awaitTerminationMillisWhe 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; + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/LitePullConsumer.java b/client/src/main/java/org/apache/rocketmq/client/consumer/LitePullConsumer.java index 25b11046f84..6c6a5970a60 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/LitePullConsumer.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/LitePullConsumer.java @@ -16,13 +16,15 @@ */ package org.apache.rocketmq.client.consumer; -import java.util.Collection; -import java.util.List; - import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + public interface LitePullConsumer { /** @@ -42,6 +44,12 @@ public interface LitePullConsumer { */ 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 * @@ -51,6 +59,14 @@ public interface LitePullConsumer { */ 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. * @@ -66,6 +82,14 @@ public interface LitePullConsumer { */ 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). @@ -74,6 +98,15 @@ public interface LitePullConsumer { */ 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); + /** * Fetch data for the topics or partitions specified using assign API * @@ -155,11 +188,45 @@ public interface LitePullConsumer { */ Long offsetForTimestamp(MessageQueue messageQueue, Long timestamp) throws MQClientException; + @Deprecated /** - * Manually commit consume offset. + * 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. * diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/MQPullConsumer.java b/client/src/main/java/org/apache/rocketmq/client/consumer/MQPullConsumer.java index a8e96283f9d..868ee93ff8a 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/MQPullConsumer.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/MQPullConsumer.java @@ -113,6 +113,13 @@ void pull(final MessageQueue mq, final String subExpression, final long offset, 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 */ diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/MQPullConsumerScheduleService.java b/client/src/main/java/org/apache/rocketmq/client/consumer/MQPullConsumerScheduleService.java index 6a5e714fbba..798162cc581 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/MQPullConsumerScheduleService.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/MQPullConsumerScheduleService.java @@ -24,13 +24,13 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.exception.MQClientException; -import org.apache.rocketmq.client.log.ClientLogger; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.NamespaceUtil; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.logging.InternalLogger; 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. @@ -38,14 +38,14 @@ * DefaultLitePullConsumer} is recommend to use in the scenario of actively pulling messages. */ public class MQPullConsumerScheduleService { - private final InternalLogger log = ClientLogger.getLog(); + private final Logger log = LoggerFactory.getLogger(MQPullConsumerScheduleService.class); private final MessageQueueListener messageQueueListener = new MessageQueueListenerImpl(); private final ConcurrentMap taskTable = - new ConcurrentHashMap(); + new ConcurrentHashMap<>(); private DefaultMQPullConsumer defaultMQPullConsumer; private int pullThreadNums = 20; private ConcurrentMap callbackTable = - new ConcurrentHashMap(); + new ConcurrentHashMap<>(); private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor; public MQPullConsumerScheduleService(final String consumerGroup) { diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/PopCallback.java b/client/src/main/java/org/apache/rocketmq/client/consumer/PopCallback.java new file mode 100644 index 00000000000..4932e7485ba --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/PopCallback.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/PopResult.java b/client/src/main/java/org/apache/rocketmq/client/consumer/PopResult.java new file mode 100644 index 00000000000..6423e90e491 --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/PopResult.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + "]"; + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/PopStatus.java b/client/src/main/java/org/apache/rocketmq/client/consumer/PopStatus.java new file mode 100644 index 00000000000..17dda9a2001 --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/PopStatus.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 realease + */ + NO_NEW_MSG, + /** + * polling pool is full, do not try again immediately. + */ + POLLING_FULL, + /** + * polling time out but no message find + */ + POLLING_NOT_FOUND +} diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/PullResult.java b/client/src/main/java/org/apache/rocketmq/client/consumer/PullResult.java index 30d995270c9..d8875421b1d 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/PullResult.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/PullResult.java @@ -26,6 +26,7 @@ public class PullResult { private final long maxOffset; private List msgFoundList; + public PullResult(PullStatus pullStatus, long nextBeginOffset, long minOffset, long maxOffset, List msgFoundList) { super(); diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AbstractAllocateMessageQueueStrategy.java b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AbstractAllocateMessageQueueStrategy.java new file mode 100644 index 00000000000..50d3cbe4661 --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AbstractAllocateMessageQueueStrategy.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMachineRoomNearby.java b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMachineRoomNearby.java index 8e9267459aa..08e95dc5880 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMachineRoomNearby.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMachineRoomNearby.java @@ -23,9 +23,7 @@ import java.util.TreeMap; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; -import org.apache.rocketmq.client.log.ClientLogger; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.logging.InternalLogger; /** * An allocate strategy proxy for based on machine room nearside priority. An actual allocate strategy can be @@ -35,8 +33,7 @@ * 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 implements AllocateMessageQueueStrategy { - private final InternalLogger log = ClientLogger.getLog(); +public class AllocateMachineRoomNearby extends AbstractAllocateMessageQueueStrategy { private final AllocateMessageQueueStrategy allocateMessageQueueStrategy;//actual allocate strategy private final MachineRoomResolver machineRoomResolver; @@ -58,32 +55,19 @@ public AllocateMachineRoomNearby(AllocateMessageQueueStrategy allocateMessageQue @Override public List allocate(String consumerGroup, String currentCID, List mqAll, List cidAll) { - if (currentCID == null || currentCID.length() < 1) { - throw new IllegalArgumentException("currentCID is empty"); - } - if (mqAll == null || mqAll.isEmpty()) { - throw new IllegalArgumentException("mqAll is null or mqAll empty"); - } - if (cidAll == null || cidAll.isEmpty()) { - 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); + List result = new ArrayList<>(); + if (!check(consumerGroup, currentCID, mqAll, cidAll)) { return result; } //group mq by machine room - Map> mr2Mq = new TreeMap>(); + 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.put(brokerMachineRoom, new ArrayList<>()); } mr2Mq.get(brokerMachineRoom).add(mq); } else { @@ -92,12 +76,12 @@ public List allocate(String consumerGroup, String currentCID, List } //group consumer by machine room - Map> mr2c = new TreeMap>(); + 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.put(consumerMachineRoom, new ArrayList<>()); } mr2c.get(consumerMachineRoom).add(cid); } else { @@ -105,7 +89,7 @@ public List allocate(String consumerGroup, String currentCID, List } } - List allocateResults = new ArrayList(); + List allocateResults = new ArrayList<>(); //1.allocate the mq that deploy in the same machine room with the current consumer String currentMachineRoom = machineRoomResolver.consumerDeployIn(currentCID); diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragely.java b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragely.java index 155e692ad0b..75e5d1c218b 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragely.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragely.java @@ -18,36 +18,19 @@ import java.util.ArrayList; import java.util.List; -import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; -import org.apache.rocketmq.client.log.ClientLogger; -import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.common.message.MessageQueue; /** * Average Hashing queue algorithm */ -public class AllocateMessageQueueAveragely implements AllocateMessageQueueStrategy { - private final InternalLogger log = ClientLogger.getLog(); +public class AllocateMessageQueueAveragely extends AbstractAllocateMessageQueueStrategy { @Override public List allocate(String consumerGroup, String currentCID, List mqAll, List cidAll) { - if (currentCID == null || currentCID.length() < 1) { - throw new IllegalArgumentException("currentCID is empty"); - } - if (mqAll == null || mqAll.isEmpty()) { - throw new IllegalArgumentException("mqAll is null or mqAll empty"); - } - if (cidAll == null || cidAll.isEmpty()) { - 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); + List result = new ArrayList<>(); + if (!check(consumerGroup, currentCID, mqAll, cidAll)) { return result; } diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircle.java b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircle.java index fe78f0a6bbf..cc618a81acf 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircle.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircle.java @@ -18,36 +18,19 @@ import java.util.ArrayList; import java.util.List; -import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; -import org.apache.rocketmq.client.log.ClientLogger; -import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.common.message.MessageQueue; /** * Cycle average Hashing queue algorithm */ -public class AllocateMessageQueueAveragelyByCircle implements AllocateMessageQueueStrategy { - private final InternalLogger log = ClientLogger.getLog(); +public class AllocateMessageQueueAveragelyByCircle extends AbstractAllocateMessageQueueStrategy { @Override public List allocate(String consumerGroup, String currentCID, List mqAll, List cidAll) { - if (currentCID == null || currentCID.length() < 1) { - throw new IllegalArgumentException("currentCID is empty"); - } - if (mqAll == null || mqAll.isEmpty()) { - throw new IllegalArgumentException("mqAll is null or mqAll empty"); - } - if (cidAll == null || cidAll.isEmpty()) { - 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); + List result = new ArrayList<>(); + if (!check(consumerGroup, currentCID, mqAll, cidAll)) { return result; } diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByConfig.java b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByConfig.java index e548803d0d0..5866e95dd40 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByConfig.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByConfig.java @@ -17,10 +17,9 @@ package org.apache.rocketmq.client.consumer.rebalance; import java.util.List; -import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.common.message.MessageQueue; -public class AllocateMessageQueueByConfig implements AllocateMessageQueueStrategy { +public class AllocateMessageQueueByConfig extends AbstractAllocateMessageQueueStrategy { private List messageQueueList; @Override diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByMachineRoom.java b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByMachineRoom.java index 42a0be1dd04..4a2ba888a92 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByMachineRoom.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByMachineRoom.java @@ -19,35 +19,27 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -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; /** * Computer room Hashing queue algorithm, such as Alipay logic room */ -public class AllocateMessageQueueByMachineRoom implements AllocateMessageQueueStrategy { +public class AllocateMessageQueueByMachineRoom extends AbstractAllocateMessageQueueStrategy { private Set consumeridcs; @Override public 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 (!check(consumerGroup, currentCID, mqAll, cidAll)) { + return result; } - List result = new ArrayList(); int currentIndex = cidAll.indexOf(currentCID); if (currentIndex < 0) { return result; } - List premqAll = new ArrayList(); + List premqAll = new ArrayList<>(); for (MessageQueue mq : mqAll) { String[] temp = mq.getBrokerName().split("@"); if (temp.length == 2 && consumeridcs.contains(temp[0])) { diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueConsistentHash.java b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueConsistentHash.java index 65dcf799271..eea19ed7fe6 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueConsistentHash.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueConsistentHash.java @@ -19,19 +19,15 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; -import org.apache.rocketmq.client.log.ClientLogger; 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.logging.InternalLogger; import org.apache.rocketmq.common.message.MessageQueue; /** * Consistent Hashing queue algorithm */ -public class AllocateMessageQueueConsistentHash implements AllocateMessageQueueStrategy { - private final InternalLogger log = ClientLogger.getLog(); +public class AllocateMessageQueueConsistentHash extends AbstractAllocateMessageQueueStrategy { private final int virtualNodeCnt; private final HashFunction customHashFunction; @@ -56,38 +52,24 @@ public AllocateMessageQueueConsistentHash(int virtualNodeCnt, HashFunction custo public List allocate(String consumerGroup, String currentCID, List mqAll, List cidAll) { - if (currentCID == null || currentCID.length() < 1) { - throw new IllegalArgumentException("currentCID is empty"); - } - if (mqAll == null || mqAll.isEmpty()) { - throw new IllegalArgumentException("mqAll is null or mqAll empty"); - } - if (cidAll == null || cidAll.isEmpty()) { - 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); + List result = new ArrayList<>(); + if (!check(consumerGroup, currentCID, mqAll, cidAll)) { return result; } - Collection cidNodes = new ArrayList(); + 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); + router = new ConsistentHashRouter<>(cidNodes, virtualNodeCnt, customHashFunction); } else { - router = new ConsistentHashRouter(cidNodes, virtualNodeCnt); + router = new ConsistentHashRouter<>(cidNodes, virtualNodeCnt); } - List results = new ArrayList(); + List results = new ArrayList<>(); for (MessageQueue mq : mqAll) { ClientNode clientNode = router.routeNode(mq.toString()); if (clientNode != null && currentCID.equals(clientNode.getKey())) { diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/store/LocalFileOffsetStore.java b/client/src/main/java/org/apache/rocketmq/client/consumer/store/LocalFileOffsetStore.java index d380ba058a4..832888dbeba 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/store/LocalFileOffsetStore.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/store/LocalFileOffsetStore.java @@ -28,13 +28,13 @@ 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.log.ClientLogger; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.help.FAQUrl; -import org.apache.rocketmq.logging.InternalLogger; 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 @@ -43,12 +43,12 @@ 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 InternalLogger log = ClientLogger.getLog(); + 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(); + new ConcurrentHashMap<>(); public LocalFileOffsetStore(MQClientInstance mQClientFactory, String groupName) { this.mQClientFactory = mQClientFactory; @@ -169,7 +169,7 @@ public void updateConsumeOffsetToBroker(final MessageQueue mq, final long offset @Override public Map cloneOffsetTable(String topic) { - Map cloneOffsetTable = new HashMap(); + 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())) { diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/store/OffsetSerializeWrapper.java b/client/src/main/java/org/apache/rocketmq/client/consumer/store/OffsetSerializeWrapper.java index 7dfd97af6af..85fe0d43981 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/store/OffsetSerializeWrapper.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/store/OffsetSerializeWrapper.java @@ -27,7 +27,7 @@ */ public class OffsetSerializeWrapper extends RemotingSerializable { private ConcurrentMap offsetTable = - new ConcurrentHashMap(); + new ConcurrentHashMap<>(); public ConcurrentMap getOffsetTable() { return offsetTable; diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/store/RemoteBrokerOffsetStore.java b/client/src/main/java/org/apache/rocketmq/client/consumer/store/RemoteBrokerOffsetStore.java index 15b5becfd48..900e8221140 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/store/RemoteBrokerOffsetStore.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/store/RemoteBrokerOffsetStore.java @@ -25,26 +25,27 @@ 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.exception.OffsetNotFoundException; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.factory.MQClientInstance; -import org.apache.rocketmq.client.log.ClientLogger; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; -import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.header.QueryConsumerOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.UpdateConsumerOffsetRequestHeader; 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.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * Remote storage implementation */ public class RemoteBrokerOffsetStore implements OffsetStore { - private final static InternalLogger log = ClientLogger.getLog(); + private final static Logger log = LoggerFactory.getLogger(RemoteBrokerOffsetStore.class); private final MQClientInstance mQClientFactory; private final String groupName; private ConcurrentMap offsetTable = - new ConcurrentHashMap(); + new ConcurrentHashMap<>(); public RemoteBrokerOffsetStore(MQClientInstance mQClientFactory, String groupName) { this.mQClientFactory = mQClientFactory; @@ -89,12 +90,11 @@ public long readOffset(final MessageQueue mq, final ReadOffsetType type) { case READ_FROM_STORE: { try { long brokerOffset = this.fetchConsumeOffsetFromBroker(mq); - AtomicLong offset = new AtomicLong(brokerOffset); - this.updateOffset(mq, offset.get(), false); + this.updateOffset(mq, brokerOffset, false); return brokerOffset; } // No offset in broker - catch (MQBrokerException e) { + catch (OffsetNotFoundException e) { return -1; } //Other exceptions @@ -108,7 +108,7 @@ public long readOffset(final MessageQueue mq, final ReadOffsetType type) { } } - return -1; + return -3; } @Override @@ -116,7 +116,7 @@ public void persistAll(Set mqs) { if (null == mqs || mqs.isEmpty()) return; - final HashSet unusedMQ = new HashSet(); + final HashSet unusedMQ = new HashSet<>(); for (Map.Entry entry : this.offsetTable.entrySet()) { MessageQueue mq = entry.getKey(); @@ -174,7 +174,7 @@ public void removeOffset(MessageQueue mq) { @Override public Map cloneOffsetTable(String topic) { - Map cloneOffsetTable = new HashMap(); + 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())) { @@ -199,10 +199,10 @@ private void updateConsumeOffsetToBroker(MessageQueue mq, long offset) throws Re @Override public void updateConsumeOffsetToBroker(MessageQueue mq, long offset, boolean isOneway) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { - FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), MixAll.MASTER_ID, true); + 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(mq.getBrokerName(), MixAll.MASTER_ID, false); + findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), MixAll.MASTER_ID, false); } if (findBrokerResult != null) { @@ -211,6 +211,7 @@ public void updateConsumeOffsetToBroker(MessageQueue mq, long offset, boolean is requestHeader.setConsumerGroup(this.groupName); requestHeader.setQueueId(mq.getQueueId()); requestHeader.setCommitOffset(offset); + requestHeader.setBname(mq.getBrokerName()); if (isOneway) { this.mQClientFactory.getMQClientAPIImpl().updateConsumerOffsetOneway( @@ -226,11 +227,10 @@ public void updateConsumeOffsetToBroker(MessageQueue mq, long offset, boolean is private long fetchConsumeOffsetFromBroker(MessageQueue mq) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { - FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), MixAll.MASTER_ID, true); + 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(mq.getBrokerName(), MixAll.MASTER_ID, false); + findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), MixAll.MASTER_ID, false); } if (findBrokerResult != null) { @@ -238,6 +238,7 @@ private long fetchConsumeOffsetFromBroker(MessageQueue mq) throws RemotingExcept requestHeader.setTopic(mq.getTopic()); requestHeader.setConsumerGroup(this.groupName); requestHeader.setQueueId(mq.getQueueId()); + requestHeader.setBname(mq.getBrokerName()); return this.mQClientFactory.getMQClientAPIImpl().queryConsumerOffset( findBrokerResult.getBrokerAddr(), requestHeader, 1000 * 5); diff --git a/client/src/main/java/org/apache/rocketmq/client/exception/MQBrokerException.java b/client/src/main/java/org/apache/rocketmq/client/exception/MQBrokerException.java index f07a38b81f0..7870ff1931b 100644 --- a/client/src/main/java/org/apache/rocketmq/client/exception/MQBrokerException.java +++ b/client/src/main/java/org/apache/rocketmq/client/exception/MQBrokerException.java @@ -25,6 +25,12 @@ public class MQBrokerException extends Exception { 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)); diff --git a/client/src/main/java/org/apache/rocketmq/client/exception/MQClientException.java b/client/src/main/java/org/apache/rocketmq/client/exception/MQClientException.java index f4534742d53..9bbcce21783 100644 --- a/client/src/main/java/org/apache/rocketmq/client/exception/MQClientException.java +++ b/client/src/main/java/org/apache/rocketmq/client/exception/MQClientException.java @@ -37,6 +37,13 @@ public MQClientException(int responseCode, String errorMessage) { 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; } diff --git a/client/src/main/java/org/apache/rocketmq/client/exception/OffsetNotFoundException.java b/client/src/main/java/org/apache/rocketmq/client/exception/OffsetNotFoundException.java new file mode 100644 index 00000000000..e73bbbf1508 --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/exception/OffsetNotFoundException.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/BaseInvokeCallback.java b/client/src/main/java/org/apache/rocketmq/client/impl/BaseInvokeCallback.java new file mode 100644 index 00000000000..80188832eb8 --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/impl/BaseInvokeCallback.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.remoting.InvokeCallback; +import org.apache.rocketmq.remoting.netty.ResponseFuture; + +public abstract class BaseInvokeCallback implements InvokeCallback { + private final MQClientAPIImpl mqClientAPI; + + public BaseInvokeCallback(MQClientAPIImpl mqClientAPI) { + this.mqClientAPI = mqClientAPI; + } + + @Override + public void operationComplete(final ResponseFuture responseFuture) { + mqClientAPI.execRpcHooksAfterRequest(responseFuture); + onComplete(responseFuture); + } + + public abstract void onComplete(final ResponseFuture responseFuture); +} diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java b/client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java index b90a5b7908e..31b879ffed0 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java @@ -24,40 +24,41 @@ 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.log.ClientLogger; 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.protocol.NamespaceUtil; -import org.apache.rocketmq.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult; -import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo; -import org.apache.rocketmq.common.protocol.body.GetConsumerStatusBody; -import org.apache.rocketmq.common.protocol.body.ResetOffsetBody; -import org.apache.rocketmq.common.protocol.header.CheckTransactionStateRequestHeader; -import org.apache.rocketmq.common.protocol.header.ConsumeMessageDirectlyResultRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumerRunningInfoRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumerStatusRequestHeader; -import org.apache.rocketmq.common.protocol.header.NotifyConsumerIdsChangedRequestHeader; -import org.apache.rocketmq.common.protocol.header.ReplyMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.ResetOffsetRequestHeader; import org.apache.rocketmq.common.sysflag.MessageSysFlag; -import org.apache.rocketmq.logging.InternalLogger; +import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.remoting.common.RemotingHelper; -import org.apache.rocketmq.remoting.common.RemotingUtil; import org.apache.rocketmq.remoting.exception.RemotingCommandException; -import org.apache.rocketmq.remoting.netty.AsyncNettyRequestProcessor; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; +import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.RemotingCommand; - -public class ClientRemotingProcessor extends AsyncNettyRequestProcessor implements NettyRequestProcessor { - private final InternalLogger log = ClientLogger.getLog(); +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) { @@ -118,13 +119,13 @@ public RemotingCommand checkTransactionState(ChannelHandlerContext ctx, final String addr = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); producer.checkTransactionState(addr, messageExt, requestHeader); } else { - log.debug("checkTransactionState, pick producer by group[{}] failed", group); + logger.debug("checkTransactionState, pick producer by group[{}] failed", group); } } else { - log.warn("checkTransactionState, pick producer group failed"); + logger.warn("checkTransactionState, pick producer group failed"); } } else { - log.warn("checkTransactionState, decode message failed"); + logger.warn("checkTransactionState, decode message failed"); } return null; @@ -135,12 +136,12 @@ public RemotingCommand notifyConsumerIdsChanged(ChannelHandlerContext ctx, try { final NotifyConsumerIdsChangedRequestHeader requestHeader = (NotifyConsumerIdsChangedRequestHeader) request.decodeCommandCustomHeader(NotifyConsumerIdsChangedRequestHeader.class); - log.info("receive broker's notification[{}], the consumer group: {} changed, rebalance immediately", + logger.info("receive broker's notification[{}], the consumer group: {} changed, rebalance immediately", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.getConsumerGroup()); this.mqClientFactory.rebalanceImmediately(); } catch (Exception e) { - log.error("notifyConsumerIdsChanged exception", RemotingHelper.exceptionSimpleDesc(e)); + logger.error("notifyConsumerIdsChanged exception", UtilAll.exceptionSimpleDesc(e)); } return null; } @@ -149,10 +150,10 @@ public RemotingCommand resetOffset(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final ResetOffsetRequestHeader requestHeader = (ResetOffsetRequestHeader) request.decodeCommandCustomHeader(ResetOffsetRequestHeader.class); - log.info("invoke reset offset operation from broker. brokerAddr={}, topic={}, group={}, timestamp={}", + 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(); + Map offsetTable = new HashMap<>(); if (request.getBody() != null) { ResetOffsetBody body = ResetOffsetBody.decode(request.getBody(), ResetOffsetBody.class); offsetTable = body.getOffsetTable(); @@ -237,19 +238,21 @@ private RemotingCommand receiveReplyMessage(ChannelHandlerContext ctx, msg.setStoreTimestamp(requestHeader.getStoreTimestamp()); if (requestHeader.getBornHost() != null) { - msg.setBornHost(RemotingUtil.string2SocketAddress(requestHeader.getBornHost())); + msg.setBornHost(NetworkUtil.string2SocketAddress(requestHeader.getBornHost())); } if (requestHeader.getStoreHost() != null) { - msg.setStoreHost(RemotingUtil.string2SocketAddress(requestHeader.getStoreHost())); + msg.setStoreHost(NetworkUtil.string2SocketAddress(requestHeader.getStoreHost())); } byte[] body = request.getBody(); - if ((requestHeader.getSysFlag() & MessageSysFlag.COMPRESSED_FLAG) == MessageSysFlag.COMPRESSED_FLAG) { + int sysFlag = requestHeader.getSysFlag(); + if ((sysFlag & MessageSysFlag.COMPRESSED_FLAG) == MessageSysFlag.COMPRESSED_FLAG) { try { - body = UtilAll.uncompress(body); + Compressor compressor = CompressorFactory.getCompressor(MessageSysFlag.getCompressionType(sysFlag)); + body = compressor.decompress(body); } catch (IOException e) { - log.warn("err when uncompress constant", e); + logger.warn("err when uncompress constant", e); } } msg.setBody(body); @@ -258,14 +261,14 @@ private RemotingCommand receiveReplyMessage(ChannelHandlerContext ctx, MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REPLY_MESSAGE_ARRIVE_TIME, String.valueOf(receiveTime)); msg.setBornTimestamp(requestHeader.getBornTimestamp()); msg.setReconsumeTimes(requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes()); - log.debug("receive reply message :{}", msg); + logger.debug("receive reply message :{}", msg); processReplyMessage(msg); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); } catch (Exception e) { - log.warn("unknown err when receiveReplyMsg", e); + logger.warn("unknown err when receiveReplyMsg", e); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("process reply message fail"); } @@ -285,7 +288,7 @@ private void processReplyMessage(MessageExt replyMsg) { } } else { String bornHost = replyMsg.getBornHostString(); - log.warn(String.format("receive reply message, but not matched any request, CorrelationId: %s , reply from host: %s", + logger.warn(String.format("receive reply message, but not matched any request, CorrelationId: %s , reply from host: %s", correlationId, bornHost)); } } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java index ba4eafae97e..33fc44fd635 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java @@ -21,46 +21,45 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.Set; +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.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.client.log.ClientLogger; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.help.FAQUrl; -import org.apache.rocketmq.common.protocol.NamespaceUtil; -import org.apache.rocketmq.logging.InternalLogger; -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.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.QueryMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.QueryMessageResponseHeader; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; +import org.apache.rocketmq.common.utils.NetworkUtil; import org.apache.rocketmq.remoting.InvokeCallback; -import org.apache.rocketmq.remoting.common.RemotingUtil; 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; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class MQAdminImpl { - private final InternalLogger log = ClientLogger.getLog(); + private static final Logger log = LoggerFactory.getLogger(MQAdminImpl.class); private final MQClientInstance mQClientFactory; private long timeoutMillis = 6000; @@ -77,10 +76,11 @@ public void setTimeoutMillis(long timeoutMillis) { } public void createTopic(String key, String newTopic, int queueNum) throws MQClientException { - createTopic(key, newTopic, queueNum, 0); + createTopic(key, newTopic, queueNum, 0, null); } - public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag) throws MQClientException { + public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, + Map attributes) throws MQClientException { try { Validators.checkTopic(newTopic); Validators.isSystemTopic(newTopic); @@ -101,6 +101,7 @@ public void createTopic(String key, String newTopic, int queueNum, int topicSysF topicConfig.setReadQueueNums(queueNum); topicConfig.setWriteQueueNums(queueNum); topicConfig.setTopicSysFlag(topicSysFlag); + topicConfig.setAttributes(attributes); boolean createOK = false; for (int i = 0; i < 5; i++) { @@ -153,7 +154,7 @@ public List fetchPublishMessageQueues(String topic) throws MQClien } public List parsePublishMessageQueues(List messageQueueList) { - List resultQueues = new ArrayList(); + 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())); @@ -183,16 +184,15 @@ public Set fetchSubscribeMessageQueues(String topic) throws MQClie } public long searchOffset(MessageQueue mq, long timestamp) throws MQClientException { - String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName()); + String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); if (null == brokerAddr) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); - brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName()); + brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); } if (brokerAddr != null) { try { - return this.mQClientFactory.getMQClientAPIImpl().searchOffset(brokerAddr, mq.getTopic(), mq.getQueueId(), timestamp, - timeoutMillis); + return this.mQClientFactory.getMQClientAPIImpl().searchOffset(brokerAddr, mq, timestamp, timeoutMillis); } catch (Exception e) { throw new MQClientException("Invoke Broker[" + brokerAddr + "] exception", e); } @@ -202,15 +202,15 @@ public long searchOffset(MessageQueue mq, long timestamp) throws MQClientExcepti } public long maxOffset(MessageQueue mq) throws MQClientException { - String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName()); + String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); if (null == brokerAddr) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); - brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName()); + brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); } if (brokerAddr != null) { try { - return this.mQClientFactory.getMQClientAPIImpl().getMaxOffset(brokerAddr, mq.getTopic(), mq.getQueueId(), timeoutMillis); + return this.mQClientFactory.getMQClientAPIImpl().getMaxOffset(brokerAddr, mq, timeoutMillis); } catch (Exception e) { throw new MQClientException("Invoke Broker[" + brokerAddr + "] exception", e); } @@ -220,15 +220,15 @@ public long maxOffset(MessageQueue mq) throws MQClientException { } public long minOffset(MessageQueue mq) throws MQClientException { - String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName()); + String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); if (null == brokerAddr) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); - brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName()); + brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); } if (brokerAddr != null) { try { - return this.mQClientFactory.getMQClientAPIImpl().getMinOffset(brokerAddr, mq.getTopic(), mq.getQueueId(), timeoutMillis); + return this.mQClientFactory.getMQClientAPIImpl().getMinOffset(brokerAddr, mq, timeoutMillis); } catch (Exception e) { throw new MQClientException("Invoke Broker[" + brokerAddr + "] exception", e); } @@ -238,16 +238,15 @@ public long minOffset(MessageQueue mq) throws MQClientException { } public long earliestMsgStoreTime(MessageQueue mq) throws MQClientException { - String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName()); + String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); if (null == brokerAddr) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); - brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName()); + brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(this.mQClientFactory.getBrokerNameFromMessageQueue(mq)); } if (brokerAddr != null) { try { - return this.mQClientFactory.getMQClientAPIImpl().getEarliestMsgStoretime(brokerAddr, mq.getTopic(), mq.getQueueId(), - timeoutMillis); + return this.mQClientFactory.getMQClientAPIImpl().getEarliestMsgStoretime(brokerAddr, mq, timeoutMillis); } catch (Exception e) { throw new MQClientException("Invoke Broker[" + brokerAddr + "] exception", e); } @@ -256,22 +255,21 @@ public long earliestMsgStoreTime(MessageQueue mq) throws MQClientException { throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null); } - public MessageExt viewMessage( - String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { - + public MessageExt viewMessage(String msgId) + throws RemotingException, MQBrokerException, InterruptedException, MQClientException { MessageId messageId = null; 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(RemotingUtil.socketAddress2String(messageId.getAddress()), + return this.mQClientFactory.getMQClientAPIImpl().viewMessage(NetworkUtil.socketAddress2String(messageId.getAddress()), messageId.getOffset(), timeoutMillis); } - public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) - throws MQClientException, InterruptedException { - + public QueryResult queryMessage(String topic, String key, int maxNum, long begin, + long end) throws MQClientException, + InterruptedException { return queryMessage(topic, key, maxNum, begin, end, false); } @@ -283,9 +281,22 @@ public QueryResult queryMessageByUniqKey(String topic, String uniqKey, int maxNu public MessageExt queryMessageByUniqKey(String topic, String uniqKey) throws InterruptedException, MQClientException { + return queryMessageByUniqKey(topic, uniqKey, System.currentTimeMillis() - 3L * 24 * 60L * 60L * 1000L, Long.MAX_VALUE); + } - QueryResult qr = queryMessageByUniqKey(topic, uniqKey, 32, - MessageClientIDSetter.getNearlyTimeFromID(uniqKey).getTime() - 1000, 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); if (qr != null && qr.getMessageList() != null && qr.getMessageList().size() > 0) { return qr.getMessageList().get(0); } else { @@ -294,6 +305,12 @@ public MessageExt queryMessageByUniqKey(String topic, } protected QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end, + boolean isUniqKey) throws MQClientException, + InterruptedException { + return queryMessage(null, topic, key, maxNum, begin, end, isUniqKey); + } + + protected QueryResult queryMessage(String clusterName, String topic, String key, int maxNum, long begin, long end, boolean isUniqKey) throws MQClientException, InterruptedException { TopicRouteData topicRouteData = this.mQClientFactory.getAnExistTopicRouteData(topic); @@ -303,8 +320,12 @@ protected QueryResult queryMessage(String topic, String key, int maxNum, long be } if (topicRouteData != null) { - List brokerAddrs = new LinkedList(); + List brokerAddrs = new LinkedList<>(); for (BrokerData brokerData : topicRouteData.getBrokerDatas()) { + if (clusterName != null && !clusterName.isEmpty() + && !clusterName.equals(brokerData.getCluster())) { + continue; + } String addr = brokerData.selectBrokerAddr(); if (addr != null) { brokerAddrs.add(addr); @@ -313,7 +334,7 @@ protected QueryResult queryMessage(String topic, String key, int maxNum, long be if (!brokerAddrs.isEmpty()) { final CountDownLatch countDownLatch = new CountDownLatch(brokerAddrs.size()); - final List queryResultList = new LinkedList(); + final List queryResultList = new LinkedList<>(); final ReadWriteLock lock = new ReentrantReadWriteLock(false); for (String addr : brokerAddrs) { @@ -380,7 +401,7 @@ public void operationComplete(ResponseFuture responseFuture) { } long indexLastUpdateTimestamp = 0; - List messageList = new LinkedList(); + List messageList = new LinkedList<>(); for (QueryResult qr : queryResultList) { if (qr.getIndexLastUpdateTimestamp() > indexLastUpdateTimestamp) { indexLastUpdateTimestamp = qr.getIndexLastUpdateTimestamp(); @@ -389,19 +410,7 @@ public void operationComplete(ResponseFuture responseFuture) { for (MessageExt msgExt : qr.getMessageList()) { if (isUniqKey) { if (msgExt.getMsgId().equals(key)) { - - if (messageList.size() > 0) { - - if (messageList.get(0).getStoreTimestamp() > msgExt.getStoreTimestamp()) { - - messageList.clear(); - messageList.add(msgExt); - } - - } else { - - messageList.add(msgExt); - } + messageList.add(msgExt); } else { log.warn("queryMessage by uniqKey, find message key not matched, maybe hash duplicate {}", msgExt.toString()); } @@ -411,13 +420,11 @@ public void operationComplete(ResponseFuture responseFuture) { if (keys != null) { boolean matched = false; String[] keyArray = keys.split(MessageConst.KEY_SEPARATOR); - if (keyArray != null) { - for (String k : keyArray) { - // both topic and key must be equal at the same time - if (Objects.equals(key, k) && Objects.equals(topic, msgTopic)) { - matched = true; - break; - } + for (String k : keyArray) { + // both topic and key must be equal at the same time + if (Objects.equals(key, k) && Objects.equals(topic, msgTopic)) { + matched = true; + break; } } @@ -432,8 +439,8 @@ public void operationComplete(ResponseFuture responseFuture) { } //If namespace not null , reset Topic without namespace. - for (MessageExt messageExt : messageList) { - if (null != this.mQClientFactory.getClientConfig().getNamespace()) { + if (null != this.mQClientFactory.getClientConfig().getNamespace()) { + for (MessageExt messageExt : messageList) { messageExt.setTopic(NamespaceUtil.withoutNamespace(messageExt.getTopic(), this.mQClientFactory.getClientConfig().getNamespace())); } } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java index 8506432e08f..4c9c3a1699c 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java @@ -16,8 +16,10 @@ */ package org.apache.rocketmq.client.impl; +import com.alibaba.fastjson.JSON; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -29,29 +31,37 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.ClientConfig; +import org.apache.rocketmq.client.Validators; +import org.apache.rocketmq.client.common.ClientErrorCode; +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.log.ClientLogger; 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.AclConfig; -import org.apache.rocketmq.common.DataVersion; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; +import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.PlainAccessConfig; import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.UtilAll; -import org.apache.rocketmq.common.admin.ConsumeStats; -import org.apache.rocketmq.common.admin.TopicStatsTable; +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; @@ -59,101 +69,19 @@ 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.protocol.NamespaceUtil; -import org.apache.rocketmq.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.body.BrokerStatsData; -import org.apache.rocketmq.common.protocol.body.CheckClientRequestBody; -import org.apache.rocketmq.common.protocol.body.ClusterAclVersionInfo; -import org.apache.rocketmq.common.protocol.body.ClusterInfo; -import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult; -import org.apache.rocketmq.common.protocol.body.ConsumeStatsList; -import org.apache.rocketmq.common.protocol.body.ConsumerConnection; -import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo; -import org.apache.rocketmq.common.protocol.body.GetConsumerStatusBody; -import org.apache.rocketmq.common.protocol.body.GroupList; -import org.apache.rocketmq.common.protocol.body.KVTable; -import org.apache.rocketmq.common.protocol.body.LockBatchRequestBody; -import org.apache.rocketmq.common.protocol.body.LockBatchResponseBody; -import org.apache.rocketmq.common.protocol.body.ProducerConnection; -import org.apache.rocketmq.common.protocol.body.QueryConsumeQueueResponseBody; -import org.apache.rocketmq.common.protocol.body.QueryConsumeTimeSpanBody; -import org.apache.rocketmq.common.protocol.body.QueryCorrectionOffsetBody; -import org.apache.rocketmq.common.protocol.body.QueueTimeSpan; -import org.apache.rocketmq.common.protocol.body.ResetOffsetBody; -import org.apache.rocketmq.common.protocol.body.SubscriptionGroupWrapper; -import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper; -import org.apache.rocketmq.common.protocol.body.TopicList; -import org.apache.rocketmq.common.protocol.body.UnlockBatchRequestBody; -import org.apache.rocketmq.common.protocol.header.CloneGroupOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.ConsumeMessageDirectlyResultRequestHeader; -import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader; -import org.apache.rocketmq.common.protocol.header.CreateAccessConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.CreateTopicRequestHeader; -import org.apache.rocketmq.common.protocol.header.DeleteAccessConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.DeleteSubscriptionGroupRequestHeader; -import org.apache.rocketmq.common.protocol.header.DeleteTopicRequestHeader; -import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetBrokerAclConfigResponseHeader; -import org.apache.rocketmq.common.protocol.header.GetBrokerClusterAclConfigResponseBody; -import org.apache.rocketmq.common.protocol.header.GetConsumeStatsInBrokerHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumeStatsRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumerConnectionListRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupResponseBody; -import org.apache.rocketmq.common.protocol.header.GetConsumerRunningInfoRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetConsumerStatusRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetEarliestMsgStoretimeRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetEarliestMsgStoretimeResponseHeader; -import org.apache.rocketmq.common.protocol.header.GetMaxOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetMaxOffsetResponseHeader; -import org.apache.rocketmq.common.protocol.header.GetMinOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetMinOffsetResponseHeader; -import org.apache.rocketmq.common.protocol.header.GetProducerConnectionListRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetTopicStatsInfoRequestHeader; -import org.apache.rocketmq.common.protocol.header.GetTopicsByClusterRequestHeader; -import org.apache.rocketmq.common.protocol.header.PullMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.PullMessageResponseHeader; -import org.apache.rocketmq.common.protocol.header.QueryConsumeQueueRequestHeader; -import org.apache.rocketmq.common.protocol.header.QueryConsumeTimeSpanRequestHeader; -import org.apache.rocketmq.common.protocol.header.QueryConsumerOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.QueryConsumerOffsetResponseHeader; -import org.apache.rocketmq.common.protocol.header.QueryCorrectionOffsetHeader; -import org.apache.rocketmq.common.protocol.header.QueryMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.QueryTopicConsumeByWhoRequestHeader; -import org.apache.rocketmq.common.protocol.header.ResetOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.ResumeCheckHalfMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.SearchOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.SearchOffsetResponseHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeaderV2; -import org.apache.rocketmq.common.protocol.header.SendMessageResponseHeader; -import org.apache.rocketmq.common.protocol.header.UnregisterClientRequestHeader; -import org.apache.rocketmq.common.protocol.header.UpdateConsumerOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.UpdateGlobalWhiteAddrsConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.ViewBrokerStatsDataRequestHeader; -import org.apache.rocketmq.common.protocol.header.ViewMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.filtersrv.RegisterMessageFilterClassRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.AddWritePermOfBrokerRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.AddWritePermOfBrokerResponseHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.DeleteKVConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.DeleteTopicFromNamesrvRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.GetKVConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.GetKVConfigResponseHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.GetKVListByNamespaceRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.GetRouteInfoRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.PutKVConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.WipeWritePermOfBrokerRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.WipeWritePermOfBrokerResponseHeader; -import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; -import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig; -import org.apache.rocketmq.logging.InternalLogger; +import org.apache.rocketmq.common.sysflag.PullSysFlag; +import org.apache.rocketmq.common.topic.TopicValidator; +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; @@ -163,14 +91,142 @@ 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 com.alibaba.fastjson.JSON; - -public class MQClientAPIImpl { - - private final static InternalLogger log = ClientLogger.getLog(); +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.BrokerMemberGroup; +import org.apache.rocketmq.remoting.protocol.body.BrokerStatsData; +import org.apache.rocketmq.remoting.protocol.body.CheckClientRequestBody; +import org.apache.rocketmq.remoting.protocol.body.ClusterAclVersionInfo; +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.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.BrokerReplicasInfo; +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.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.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.CloneGroupOffsetRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.CreateAccessConfigRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.DeleteAccessConfigRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.DeleteSubscriptionGroupRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.DeleteTopicRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil; +import org.apache.rocketmq.remoting.protocol.header.GetAllProducerInfoRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.GetBrokerAclConfigResponseHeader; +import org.apache.rocketmq.remoting.protocol.header.GetBrokerClusterAclConfigResponseBody; +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.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.GetTopicsByClusterRequestHeader; +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.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.UnregisterClientRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.UpdateGlobalWhiteAddrsConfigRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.UpdateGroupForbiddenRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.ViewBrokerStatsDataRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.ViewMessageRequestHeader; +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.header.controller.admin.CleanControllerBrokerDataRequestHeader; +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.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 org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; + +import static org.apache.rocketmq.remoting.protocol.RemotingSysResponseCode.SUCCESS; + +public class MQClientAPIImpl implements NameServerUpdateCallback { + private final static Logger log = LoggerFactory.getLogger(MQClientAPIImpl.class); private static boolean sendSmartMsg = Boolean.parseBoolean(System.getProperty("org.apache.rocketmq.client.sendSmartMsg", "true")); @@ -188,11 +244,17 @@ public MQClientAPIImpl(final NettyClientConfig nettyClientConfig, final ClientRemotingProcessor clientRemotingProcessor, RPCHook rpcHook, final ClientConfig clientConfig) { this.clientConfig = clientConfig; - topAddressing = new TopAddressing(MixAll.getWSAddr(), clientConfig.getUnitName()); + topAddressing = new DefaultTopAddressing(MixAll.getWSAddr(), clientConfig.getUnitName()); + topAddressing.registerChangeCallBack(this); this.remotingClient = new NettyRemotingClient(nettyClientConfig, null); this.clientRemotingProcessor = clientRemotingProcessor; + // 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_CONSUMER_IDS_CHANGED, this.clientRemotingProcessor, null); @@ -219,7 +281,7 @@ public RemotingClient getRemotingClient() { public String fetchNameServerAddr() { try { String addrs = this.topAddressing.fetchNSAddr(); - if (addrs != null) { + if (!UtilAll.isBlank(addrs)) { if (!addrs.equals(this.nameSrvAddr)) { log.info("name server address changed, old=" + this.nameSrvAddr + ", new=" + addrs); this.updateNameServerAddressList(addrs); @@ -233,6 +295,19 @@ public String fetchNameServerAddr() { 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); @@ -247,9 +322,36 @@ 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, MQBrokerException, InterruptedException, MQClientException { + final long timeoutMillis) throws RemotingException, InterruptedException, MQClientException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP, null); byte[] body = RemotingSerializable.encode(config); @@ -273,6 +375,8 @@ public void createSubscriptionGroup(final String addr, final SubscriptionGroupCo 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); @@ -282,6 +386,7 @@ public void createTopic(final String addr, final String defaultTopic, final Topi 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); @@ -301,7 +406,7 @@ public void createTopic(final String addr, final String defaultTopic, final Topi public void createPlainAccessConfig(final String addr, final PlainAccessConfig plainAccessConfig, final long timeoutMillis) - throws RemotingException, MQBrokerException, InterruptedException, MQClientException { + throws RemotingException, InterruptedException, MQClientException { CreateAccessConfigRequestHeader requestHeader = new CreateAccessConfigRequestHeader(); requestHeader.setAccessKey(plainAccessConfig.getAccessKey()); requestHeader.setSecretKey(plainAccessConfig.getSecretKey()); @@ -329,7 +434,7 @@ public void createPlainAccessConfig(final String addr, final PlainAccessConfig p } public void deleteAccessConfig(final String addr, final String accessKey, final long timeoutMillis) - throws RemotingException, MQBrokerException, InterruptedException, MQClientException { + throws RemotingException, InterruptedException, MQClientException { DeleteAccessConfigRequestHeader requestHeader = new DeleteAccessConfigRequestHeader(); requestHeader.setAccessKey(accessKey); @@ -349,11 +454,12 @@ public void deleteAccessConfig(final String addr, final String accessKey, final throw new MQClientException(response.getCode(), response.getRemark()); } - public void updateGlobalWhiteAddrsConfig(final String addr, final String globalWhiteAddrs, final long timeoutMillis) + public void updateGlobalWhiteAddrsConfig(final String addr, final String globalWhiteAddrs, String aclFileFullPath, + final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { - UpdateGlobalWhiteAddrsConfigRequestHeader requestHeader = new UpdateGlobalWhiteAddrsConfigRequestHeader(); requestHeader.setGlobalWhiteAddrs(globalWhiteAddrs); + requestHeader.setAclFileFullPath(aclFileFullPath); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_GLOBAL_WHITE_ADDRS_CONFIG, requestHeader); @@ -389,9 +495,9 @@ public ClusterAclVersionInfo getBrokerClusterAclInfo(final String addr, clusterAclVersionInfo.setBrokerAddr(responseHeader.getBrokerAddr()); clusterAclVersionInfo.setAclConfigDataVersion(DataVersion.fromJson(responseHeader.getVersion(), DataVersion.class)); HashMap dataVersionMap = JSON.parseObject(responseHeader.getAllAclFileVersion(), HashMap.class); - Map allAclConfigDataVersion = new HashMap(); + Map allAclConfigDataVersion = new HashMap<>(dataVersionMap.size(), 1); for (Map.Entry entry : dataVersionMap.entrySet()) { - allAclConfigDataVersion.put(entry.getKey(),DataVersion.fromJson(JSON.toJSONString(entry.getValue()), DataVersion.class)); + allAclConfigDataVersion.put(entry.getKey(), DataVersion.fromJson(JSON.toJSONString(entry.getValue()), DataVersion.class)); } clusterAclVersionInfo.setAllAclConfigDataVersion(allAclConfigDataVersion); return clusterAclVersionInfo; @@ -405,7 +511,7 @@ public ClusterAclVersionInfo getBrokerClusterAclInfo(final String addr, } public AclConfig getBrokerClusterConfig(final String addr, - final long timeoutMillis) throws RemotingCommandException, InterruptedException, RemotingTimeoutException, + final long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_CLUSTER_ACL_CONFIG, null); @@ -516,6 +622,14 @@ private SendResult sendMessageSync( 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, @@ -529,7 +643,7 @@ private void sendMessageAsync( final AtomicInteger times, final SendMessageContext context, final DefaultMQProducerImpl producer - ) throws InterruptedException, RemotingException { + ) { final long beginStartTime = System.currentTimeMillis(); try { this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() { @@ -595,7 +709,7 @@ public void operationComplete(ResponseFuture responseFuture) { long cost = System.currentTimeMillis() - beginStartTime; producer.updateFaultItem(brokerName, cost, true); onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance, - retryTimesWhenSendFailed, times, ex, context, true, producer); + retryTimesWhenSendFailed, times, ex, context, true, producer); } } @@ -618,26 +732,14 @@ private void onExceptionImpl(final String brokerName, 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); - retryBrokerName = mqChosen.getBrokerName(); + retryBrokerName = instance.getBrokerNameFromMessageQueue(mqChosen); } String addr = instance.findBrokerAddressInPublish(retryBrokerName); - log.warn(String.format("async send msg by retry {} times. topic={}, brokerAddr={}, brokerName={}", tmp, msg.getTopic(), addr, - retryBrokerName), e); - try { - request.setOpaque(RemotingCommand.createNewRequestId()); - sendMessageAsync(addr, retryBrokerName, msg, timeoutMillis, request, sendCallback, topicPublishInfo, instance, - timesTotal, curTimes, context, producer); - } catch (InterruptedException e1) { - onExceptionImpl(retryBrokerName, msg, timeoutMillis, request, sendCallback, topicPublishInfo, instance, timesTotal, curTimes, e1, - context, false, producer); - } catch (RemotingTooMuchRequestException e1) { - onExceptionImpl(retryBrokerName, msg, timeoutMillis, request, sendCallback, topicPublishInfo, instance, timesTotal, curTimes, e1, - context, false, producer); - } catch (RemotingException e1) { - producer.updateFaultItem(brokerName, 3000, true); - onExceptionImpl(retryBrokerName, msg, timeoutMillis, request, sendCallback, topicPublishInfo, instance, timesTotal, curTimes, e1, - context, true, producer); - } + 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) { @@ -652,7 +754,7 @@ private void onExceptionImpl(final String brokerName, } } - private SendResult processSendResponse( + protected SendResult processSendResponse( final String brokerName, final Message msg, final RemotingCommand response, @@ -693,7 +795,8 @@ private SendResult processSendResponse( MessageQueue messageQueue = new MessageQueue(topic, brokerName, responseHeader.getQueueId()); String uniqMsgId = MessageClientIDSetter.getUniqID(msg); - if (msg instanceof MessageBatch) { + 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)); @@ -705,16 +808,12 @@ private SendResult processSendResponse( 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); + String traceOn = response.getExtFields().get(MessageConst.PROPERTY_TRACE_SWITCH); + sendResult.setTraceOn(!Boolean.FALSE.toString().equals(traceOn)); return sendResult; } @@ -725,7 +824,12 @@ public PullResult pullMessage( final CommunicationMode communicationMode, final PullCallback pullCallback ) throws RemotingException, MQBrokerException, InterruptedException { - RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader); + 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: @@ -744,6 +848,121 @@ public PullResult pullMessage( 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 BaseInvokeCallback(MQClientAPIImpl.this) { + @Override + public void onComplete(ResponseFuture responseFuture) { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + try { + PopResult + popResult = MQClientAPIImpl.this.processPopResponse(brokerName, response, requestHeader.getTopic(), requestHeader); + assert popResult != null; + popCallback.onSuccess(popResult); + } catch (Exception e) { + popCallback.onException(e); + } + } else { + if (!responseFuture.isSendRequestOK()) { + popCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause())); + } else if (responseFuture.isTimeout()) { + popCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request, + responseFuture.getCause())); + } else { + popCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause())); + } + } + } + }); + } + + public void ackMessageAsync( + final String addr, + final long timeOut, + final AckCallback ackCallback, + final AckMessageRequestHeader requestHeader // + ) throws RemotingException, MQBrokerException, InterruptedException { + final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader); + this.remotingClient.invokeAsync(addr, request, timeOut, new BaseInvokeCallback(MQClientAPIImpl.this) { + + @Override + public void onComplete(ResponseFuture responseFuture) { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + try { + AckResult ackResult = new AckResult(); + if (ResponseCode.SUCCESS == response.getCode()) { + ackResult.setStatus(AckStatus.OK); + } else { + ackResult.setStatus(AckStatus.NO_EXIST); + } + ackCallback.onSuccess(ackResult); + } catch (Exception e) { + ackCallback.onException(e); + } + } else { + if (!responseFuture.isSendRequestOK()) { + ackCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause())); + } else if (responseFuture.isTimeout()) { + ackCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request, + responseFuture.getCause())); + } else { + ackCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeOut + ". Request: " + request, responseFuture.getCause())); + } + } + + } + }); + } + + 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 BaseInvokeCallback(MQClientAPIImpl.this) { + @Override + public void onComplete(ResponseFuture responseFuture) { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + 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); + } + } else { + if (!responseFuture.isSendRequestOK()) { + ackCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause())); + } else if (responseFuture.isTimeout()) { + ackCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request, + responseFuture.getCause())); + } else { + ackCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause())); + } + } + } + }); + } + private void pullMessageAsync( final String addr, final RemotingCommand request, @@ -764,9 +983,9 @@ public void operationComplete(ResponseFuture responseFuture) { } } else { if (!responseFuture.isSendRequestOK()) { - pullCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause())); + pullCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause())); } else if (responseFuture.isTimeout()) { - pullCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request, + pullCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request, responseFuture.getCause())); } else { pullCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause())); @@ -812,7 +1031,155 @@ private PullResult processPullResponse( (PullMessageResponseHeader) response.decodeCommandCustomHeader(PullMessageResponseHeader.class); return new PullResultExt(pullStatus, responseHeader.getNextBeginOffset(), responseHeader.getMinOffset(), - responseHeader.getMaxOffset(), null, responseHeader.getSuggestWhichBrokerId(), response.getBody()); + 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, 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, Long.parseLong( + messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET))); + index = sortMap.get(queueIdKey).indexOf( + Long.parseLong(messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET))); + msgQueueOffset = msgOffsetInfo.get(queueIdKey).get(index); + if (msgQueueOffset != Long.parseLong( + messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET))) { + log.warn("Queue offset[%d] of msg is strange, not equal to the stored in msg, %s", + 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[%d] of msg is strange, not equal to the stored in msg, %s", 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; + } + + /** + * 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, LMQ topic has only 1 queue, which queue id is 0 + key = ExtraInfoUtil.getStartOffsetInfoMapKey( + messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH), 0); + if (!sortMap.containsKey(key)) { + sortMap.put(key, new ArrayList<>(4)); + } + sortMap.get(key).add( + Long.parseLong(messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET))); + continue; + } + key = ExtraInfoUtil.getStartOffsetInfoMapKey(messageExt.getTopic(), 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 long phyoffset, final long timeoutMillis) @@ -841,6 +1208,7 @@ public MessageExt viewMessage(final String addr, final long phyoffset, final lon 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 { @@ -866,11 +1234,38 @@ public long searchOffset(final String addr, final String topic, final int queueI throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } - public long getMaxOffset(final String addr, final String topic, final int queueId, final long timeoutMillis) + public long searchOffset(final String addr, final MessageQueue messageQueue, final long timestamp, + final long timeoutMillis) + throws RemotingException, MQBrokerException, InterruptedException { + SearchOffsetRequestHeader requestHeader = new SearchOffsetRequestHeader(); + requestHeader.setTopic(messageQueue.getTopic()); + requestHeader.setQueueId(messageQueue.getQueueId()); + requestHeader.setBname(messageQueue.getBrokerName()); + 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 getMaxOffset(final String addr, final MessageQueue messageQueue, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { GetMaxOffsetRequestHeader requestHeader = new GetMaxOffsetRequestHeader(); - requestHeader.setTopic(topic); - requestHeader.setQueueId(queueId); + requestHeader.setTopic(messageQueue.getTopic()); + requestHeader.setQueueId(messageQueue.getQueueId()); + requestHeader.setBname(messageQueue.getBrokerName()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_MAX_OFFSET, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), @@ -917,11 +1312,12 @@ public List getConsumerIdListByGroup( throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } - public long getMinOffset(final String addr, final String topic, final int queueId, final long timeoutMillis) + public long getMinOffset(final String addr, final MessageQueue messageQueue, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { GetMinOffsetRequestHeader requestHeader = new GetMinOffsetRequestHeader(); - requestHeader.setTopic(topic); - requestHeader.setQueueId(queueId); + requestHeader.setTopic(messageQueue.getTopic()); + requestHeader.setQueueId(messageQueue.getQueueId()); + requestHeader.setBname(messageQueue.getBrokerName()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_MIN_OFFSET, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), @@ -941,12 +1337,12 @@ public long getMinOffset(final String addr, final String topic, final int queueI throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } - public long getEarliestMsgStoretime(final String addr, final String topic, final int queueId, - final long timeoutMillis) + public long getEarliestMsgStoretime(final String addr, final MessageQueue mq, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException { GetEarliestMsgStoretimeRequestHeader requestHeader = new GetEarliestMsgStoretimeRequestHeader(); - requestHeader.setTopic(topic); - requestHeader.setQueueId(queueId); + requestHeader.setTopic(mq.getTopic()); + requestHeader.setQueueId(mq.getQueueId()); + requestHeader.setBname(mq.getBrokerName()); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_EARLIEST_MSG_STORETIME, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), @@ -980,9 +1376,11 @@ public long queryConsumerOffset( 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; } @@ -1022,7 +1420,7 @@ public void updateConsumerOffsetOneway( this.remotingClient.invokeOneway(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); } - public int sendHearbeat( + public int sendHeartbeat( final String addr, final HeartbeatData heartbeatData, final long timeoutMillis @@ -1043,6 +1441,30 @@ public int sendHearbeat( 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, null); + 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, @@ -1074,7 +1496,7 @@ public void endTransactionOneway( final EndTransactionRequestHeader requestHeader, final String remark, final long timeoutMillis - ) throws RemotingException, MQBrokerException, InterruptedException { + ) throws RemotingException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, requestHeader); request.setRemark(remark); @@ -1105,6 +1527,7 @@ public boolean registerClient(final String addr, final HeartbeatData heartbeat, public void consumerSendMessageBack( final String addr, + final String brokerName, final MessageExt msg, final String consumerGroup, final int delayLevel, @@ -1120,6 +1543,7 @@ public void consumerSendMessageBack( requestHeader.setDelayLevel(delayLevel); requestHeader.setOriginMsgId(msg.getMsgId()); requestHeader.setMaxReconsumeTimes(maxConsumeRetryTimes); + requestHeader.setBname(brokerName); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis); @@ -1258,6 +1682,26 @@ public ProducerConnection getProducerConnectionList(final String addr, final Str 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, @@ -1298,9 +1742,51 @@ public KVTable getBrokerRuntimeInfo(final String addr, final long timeoutMillis) 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, UnsupportedEncodingException { + MQBrokerException, MQClientException, UnsupportedEncodingException { + Validators.checkBrokerConfig(properties); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_BROKER_CONFIG, null); @@ -1339,33 +1825,108 @@ public Properties getBrokerConfig(final String addr, final long timeoutMillis) throw new MQBrokerException(response.getCode(), response.getRemark(), addr); } - 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; - } - + 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 TopicRouteData getDefaultTopicRouteInfoFromNameServer(final String topic, final long timeoutMillis) + 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(topic, timeoutMillis, false); + 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); } @@ -1373,7 +1934,6 @@ public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final 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); @@ -1402,7 +1962,6 @@ public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final 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()) { @@ -1426,7 +1985,6 @@ public int wipeWritePermOfBroker(final String namesrvAddr, String brokerName, 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()) { @@ -1469,7 +2027,6 @@ public void deleteTopicInBroker(final String addr, final String topic, final lon 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; @@ -1489,7 +2046,26 @@ public void deleteTopicInNameServer(final String addr, final String topic, final 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()) { @@ -1508,7 +2084,7 @@ public void deleteSubscriptionGroup(final String addr, final String groupName, f throws RemotingException, InterruptedException, MQClientException { DeleteSubscriptionGroupRequestHeader requestHeader = new DeleteSubscriptionGroupRequestHeader(); requestHeader.setGroupName(groupName); - requestHeader.setRemoveOffset(removeOffset); + requestHeader.setCleanOffset(removeOffset); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_SUBSCRIPTIONGROUP, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), @@ -1532,7 +2108,6 @@ public String getKVConfigValue(final String namespace, final String key, final l 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()) { @@ -1612,7 +2187,6 @@ public KVTable getKVListByNamespace(final String namespace, final long timeoutMi 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()) { @@ -1632,6 +2206,40 @@ public Map invokeBrokerToResetOffset(final String addr, fina 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 { @@ -1640,12 +2248,13 @@ public Map invokeBrokerToResetOffset(final String addr, fina 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; @@ -1673,7 +2282,6 @@ public Map> invokeBrokerToGetConsumerStatus(fina 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; @@ -1698,7 +2306,6 @@ public GroupList queryTopicConsumeByWho(final String addr, final String topic, f 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()) { @@ -1713,6 +2320,51 @@ public GroupList queryTopicConsumeByWho(final String addr, final String topic, f 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, @@ -1722,7 +2374,6 @@ public List queryConsumeTimeSpan(final String addr, final String 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()) { @@ -1742,7 +2393,6 @@ public TopicList getTopicsByCluster(final String cluster, final long timeoutMill 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()) { @@ -1760,38 +2410,9 @@ public TopicList getTopicsByCluster(final String cluster, final long timeoutMill throw new MQClientException(response.getCode(), response.getRemark()); } - public void registerMessageFilterClass(final String addr, - final String consumerGroup, - final String topic, - final String className, - final int classCRC, - final byte[] classBody, - final long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, - InterruptedException, MQBrokerException { - RegisterMessageFilterClassRequestHeader requestHeader = new RegisterMessageFilterClassRequestHeader(); - requestHeader.setConsumerGroup(consumerGroup); - requestHeader.setClassName(className); - requestHeader.setTopic(topic); - requestHeader.setClassCRC(classCRC); - - RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REGISTER_MESSAGE_FILTER_CLASS, requestHeader); - request.setBody(classBody); - RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); - switch (response.getCode()) { - case ResponseCode.SUCCESS: { - return; - } - default: - break; - } - - throw new MQBrokerException(response.getCode(), response.getRemark(), addr); - } - 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()) { @@ -1819,7 +2440,6 @@ public TopicList getSystemTopicList( 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; @@ -1855,6 +2475,22 @@ public boolean cleanExpiredConsumeQueue(final String addr, 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 { @@ -1903,9 +2539,11 @@ public ConsumerRunningInfo getConsumerRunningInfo(final String addr, String cons 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); @@ -1947,7 +2585,6 @@ public Map queryCorrectionOffset(final String addr, final String 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; @@ -1968,7 +2605,6 @@ public Map queryCorrectionOffset(final String addr, final String 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()) { @@ -1980,8 +2616,9 @@ public TopicList getUnitTopicList(final boolean containRetry, final long timeout Iterator it = topicList.getTopicList().iterator(); while (it.hasNext()) { String topic = it.next(); - if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) + if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { it.remove(); + } } } @@ -1998,7 +2635,6 @@ public TopicList getUnitTopicList(final boolean containRetry, final long timeout 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()) { @@ -2010,8 +2646,9 @@ public TopicList getHasUnitSubTopicList(final boolean containRetry, final long t Iterator it = topicList.getTopicList().iterator(); while (it.hasNext()) { String topic = it.next(); - if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) + if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { it.remove(); + } } } return topicList; @@ -2027,7 +2664,6 @@ public TopicList getHasUnitSubTopicList(final boolean containRetry, final long t 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()) { @@ -2039,8 +2675,9 @@ public TopicList getHasUnitSubUnUnitTopicList(final boolean containRetry, final Iterator it = topicList.getTopicList().iterator(); while (it.hasNext()) { String topic = it.next(); - if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) + if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { it.remove(); + } } } return topicList; @@ -2062,7 +2699,6 @@ public void cloneGroupOffset(final String addr, final String srcGroup, final Str 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; @@ -2085,7 +2721,6 @@ public BrokerStatsData viewBrokerStatsData(String brokerAddr, String 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; @@ -2104,8 +2739,7 @@ public BrokerStatsData viewBrokerStatsData(String brokerAddr, String statsName, } public Set getClusterList(String topic, - long timeoutMillis) throws MQClientException, RemotingConnectException, - RemotingSendRequestException, RemotingTimeoutException, InterruptedException { + long timeoutMillis) { return Collections.EMPTY_SET; } @@ -2116,7 +2750,6 @@ public ConsumeStatsList fetchConsumeStatsInBroker(String brokerAddr, boolean isO 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; @@ -2151,6 +2784,25 @@ public SubscriptionGroupWrapper getAllSubscriptionGroup(final String brokerAddr, throw new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr); } + 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 { @@ -2171,8 +2823,7 @@ public TopicConfigSerializeWrapper getAllTopicConfig(final String addr, } public void updateNameServerConfig(final Properties properties, final List nameServers, long timeoutMillis) - throws UnsupportedEncodingException, - MQBrokerException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException, + throws UnsupportedEncodingException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException { String str = MixAll.properties2String(properties); if (str == null || str.length() < 1) { @@ -2217,7 +2868,7 @@ public Map getNameServerConfig(final List nameServer RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_NAMESRV_CONFIG, null); - Map configMap = new HashMap(4); + Map configMap = new HashMap<>(4); for (String nameServer : invokeNameServers) { RemotingCommand response = this.remotingClient.invokeSync(nameServer, request, timeoutMillis); @@ -2246,7 +2897,6 @@ public QueryConsumeQueueResponseBody queryConsumeQueue(final String brokerAddr, 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; @@ -2282,7 +2932,7 @@ public void checkClientInBroker(final String brokerAddr, final String consumerGr } public boolean resumeCheckHalfMessage(final String addr, String msgId, - final long timeoutMillis) throws RemotingException, MQClientException, InterruptedException { + final long timeoutMillis) throws RemotingException, InterruptedException { ResumeCheckHalfMessageRequestHeader requestHeader = new ResumeCheckHalfMessageRequestHeader(); requestHeader.setMsgId(msgId); @@ -2300,4 +2950,299 @@ public boolean resumeCheckHalfMessage(final String addr, String msgId, 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()); + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientManager.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientManager.java index 053c049c9cd..49186633fa8 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientManager.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientManager.java @@ -21,16 +21,16 @@ 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.log.ClientLogger; -import org.apache.rocketmq.logging.InternalLogger; 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 InternalLogger log = ClientLogger.getLog(); + 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(); + new ConcurrentHashMap<>(); private MQClientManager() { diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/admin/MqClientAdminImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/admin/MqClientAdminImpl.java new file mode 100644 index 00000000000..34f066c7ddd --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/impl/admin/MqClientAdminImpl.java @@ -0,0 +1,438 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/AssignedMessageQueue.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/AssignedMessageQueue.java index 4d18a9be1b7..a57cb53b4df 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/AssignedMessageQueue.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/AssignedMessageQueue.java @@ -30,17 +30,13 @@ public class AssignedMessageQueue { private RebalanceImpl rebalanceImpl; public AssignedMessageQueue() { - assignedMessageQueueState = new ConcurrentHashMap(); + assignedMessageQueueState = new ConcurrentHashMap<>(); } public void setRebalanceImpl(RebalanceImpl rebalanceImpl) { this.rebalanceImpl = rebalanceImpl; } - public Set messageQueues() { - return assignedMessageQueueState.keySet(); - } - public boolean isPaused(MessageQueue messageQueue) { MessageQueueState messageQueueState = assignedMessageQueueState.get(messageQueue); if (messageQueueState != null) { diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyService.java index f8b74a02f9c..c915cce814e 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyService.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyService.java @@ -29,28 +29,27 @@ 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.log.ClientLogger; 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.protocol.body.CMResult; -import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.common.utils.ThreadUtils; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.remoting.common.RemotingHelper; +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 InternalLogger log = ClientLogger.getLog(); + private static final Logger log = LoggerFactory.getLogger(ConsumeMessageConcurrentlyService.class); private final DefaultMQPushConsumerImpl defaultMQPushConsumerImpl; private final DefaultMQPushConsumer defaultMQPushConsumer; private final MessageListenerConcurrently messageListener; @@ -68,24 +67,19 @@ public ConsumeMessageConcurrentlyService(DefaultMQPushConsumerImpl defaultMQPush this.defaultMQPushConsumer = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer(); this.consumerGroup = this.defaultMQPushConsumer.getConsumerGroup(); - this.consumeRequestQueue = new LinkedBlockingQueue(); + this.consumeRequestQueue = new LinkedBlockingQueue<>(); - String consumeThreadPrefix = null; - if (consumerGroup.length() > 100) { - consumeThreadPrefix = new StringBuilder("ConsumeMessageThread_").append(consumerGroup.substring(0, 100)).append("_").toString(); - } else { - consumeThreadPrefix = new StringBuilder("ConsumeMessageThread_").append(consumerGroup).append("_").toString(); - } + 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(consumeThreadPrefix)); + new ThreadFactoryImpl("ConsumeMessageThread_" + consumerGroupTag)); - this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("ConsumeMessageScheduledThread_")); - this.cleanExpireMsgExecutors = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("CleanExpireMsgScheduledThread_")); + this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("ConsumeMessageScheduledThread_" + consumerGroupTag)); + this.cleanExpireMsgExecutors = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("CleanExpireMsgScheduledThread_" + consumerGroupTag)); } public void start() { @@ -139,7 +133,8 @@ public ConsumeMessageDirectlyResult consumeMessageDirectly(MessageExt msg, Strin result.setOrder(false); result.setAutoCommit(true); - List msgs = new ArrayList(); + msg.setBrokerName(brokerName); + List msgs = new ArrayList<>(); msgs.add(msg); MessageQueue mq = new MessageQueue(); mq.setBrokerName(brokerName); @@ -172,10 +167,10 @@ public ConsumeMessageDirectlyResult consumeMessageDirectly(MessageExt msg, Strin } } catch (Throwable e) { result.setConsumeResult(CMResult.CR_THROW_EXCEPTION); - result.setRemark(RemotingHelper.exceptionSimpleDesc(e)); + result.setRemark(UtilAll.exceptionSimpleDesc(e)); log.warn(String.format("consumeMessageDirectly exception: %s Group: %s Msgs: %s MQ: %s", - RemotingHelper.exceptionSimpleDesc(e), + UtilAll.exceptionSimpleDesc(e), ConsumeMessageConcurrentlyService.this.consumerGroup, msgs, mq), e); @@ -204,7 +199,7 @@ public void submitConsumeRequest( } } else { for (int total = 0; total < msgs.size(); ) { - List msgThis = new ArrayList(consumeBatchSize); + List msgThis = new ArrayList<>(consumeBatchSize); for (int i = 0; i < consumeBatchSize; i++, total++) { if (total < msgs.size()) { msgThis.add(msgs.get(total)); @@ -227,6 +222,12 @@ public void submitConsumeRequest( } } + @Override + public void submitPopConsumeRequest(final List msgs, + final PopProcessQueue processQueue, + final MessageQueue messageQueue) { + throw new UnsupportedOperationException(); + } private void cleanExpireMsg() { Iterator> it = @@ -275,9 +276,16 @@ public void processConsumeResult( } break; case CLUSTERING: - List msgBackFailed = new ArrayList(consumeRequest.getMsgs().size()); + 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); @@ -311,10 +319,10 @@ public boolean sendMessageBack(final MessageExt msg, final ConsumeConcurrentlyCo // Wrap topic with namespace before sending back message. msg.setTopic(this.defaultMQPushConsumer.withNamespace(msg.getTopic())); try { - this.defaultMQPushConsumerImpl.sendMessageBack(msg, delayLevel, context.getMessageQueue().getBrokerName()); + this.defaultMQPushConsumerImpl.sendMessageBack(msg, delayLevel, this.defaultMQPushConsumer.queueWithNamespace(context.getMessageQueue())); return true; } catch (Exception e) { - log.error("sendMessageBack exception, group: " + this.consumerGroup + " msg: " + msg.toString(), e); + log.error("sendMessageBack exception, group: " + this.consumerGroup + " msg: " + msg, e); } return false; @@ -376,6 +384,7 @@ public void run() { 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; @@ -383,7 +392,7 @@ public void run() { consumeMessageContext = new ConsumeMessageContext(); consumeMessageContext.setNamespace(defaultMQPushConsumer.getNamespace()); consumeMessageContext.setConsumerGroup(defaultMQPushConsumer.getConsumerGroup()); - consumeMessageContext.setProps(new HashMap()); + consumeMessageContext.setProps(new HashMap<>()); consumeMessageContext.setMq(messageQueue); consumeMessageContext.setMsgList(msgs); consumeMessageContext.setSuccess(false); @@ -402,7 +411,7 @@ public void run() { status = listener.consumeMessage(Collections.unmodifiableList(msgs), context); } catch (Throwable e) { log.warn(String.format("consumeMessage exception: %s Group: %s Msgs: %s MQ: %s", - RemotingHelper.exceptionSimpleDesc(e), + UtilAll.exceptionSimpleDesc(e), ConsumeMessageConcurrentlyService.this.consumerGroup, msgs, messageQueue), e); diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java index cc3aee4ef59..f9c00839c50 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java @@ -26,7 +26,6 @@ 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; @@ -34,26 +33,25 @@ 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.log.ClientLogger; 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.protocol.NamespaceUtil; -import org.apache.rocketmq.common.utils.ThreadUtils; -import org.apache.rocketmq.logging.InternalLogger; 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.protocol.body.CMResult; -import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.remoting.common.RemotingHelper; +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 InternalLogger log = ClientLogger.getLog(); + 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; @@ -73,23 +71,18 @@ public ConsumeMessageOrderlyService(DefaultMQPushConsumerImpl defaultMQPushConsu this.defaultMQPushConsumer = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer(); this.consumerGroup = this.defaultMQPushConsumer.getConsumerGroup(); - this.consumeRequestQueue = new LinkedBlockingQueue(); + this.consumeRequestQueue = new LinkedBlockingQueue<>(); - String consumeThreadPrefix = null; - if (consumerGroup.length() > 100) { - consumeThreadPrefix = new StringBuilder("ConsumeMessageThread_").append(consumerGroup.substring(0, 100)).append("_").toString(); - } else { - consumeThreadPrefix = new StringBuilder("ConsumeMessageThread_").append(consumerGroup).append("_").toString(); - } + 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(consumeThreadPrefix)); + new ThreadFactoryImpl("ConsumeMessageThread_" + consumerGroupTag)); - this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("ConsumeMessageScheduledThread_")); + this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("ConsumeMessageScheduledThread_" + consumerGroupTag)); } public void start() { @@ -147,7 +140,7 @@ public ConsumeMessageDirectlyResult consumeMessageDirectly(MessageExt msg, Strin ConsumeMessageDirectlyResult result = new ConsumeMessageDirectlyResult(); result.setOrder(true); - List msgs = new ArrayList(); + List msgs = new ArrayList<>(); msgs.add(msg); MessageQueue mq = new MessageQueue(); mq.setBrokerName(brokerName); @@ -186,10 +179,10 @@ public ConsumeMessageDirectlyResult consumeMessageDirectly(MessageExt msg, Strin } } catch (Throwable e) { result.setConsumeResult(CMResult.CR_THROW_EXCEPTION); - result.setRemark(RemotingHelper.exceptionSimpleDesc(e)); + result.setRemark(UtilAll.exceptionSimpleDesc(e)); log.warn(String.format("consumeMessageDirectly exception: %s Group: %s Msgs: %s MQ: %s", - RemotingHelper.exceptionSimpleDesc(e), + UtilAll.exceptionSimpleDesc(e), ConsumeMessageOrderlyService.this.consumerGroup, msgs, mq), e); @@ -215,6 +208,13 @@ public void submitConsumeRequest( } } + @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(); @@ -378,17 +378,17 @@ 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.setProperties(newMsg, msg.getProperties()); MessageAccessor.putProperty(newMsg, MessageConst.PROPERTY_RETRY_TOPIC, msg.getTopic()); - MessageAccessor.setReconsumeTime(newMsg, String.valueOf(msg.getReconsumeTimes())); + 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.defaultMQPushConsumer.getDefaultMQPushConsumerImpl().getmQClientFactory().getDefaultMQProducer().send(newMsg); + this.defaultMQPushConsumerImpl.getmQClientFactory().getDefaultMQProducer().send(newMsg); return true; } catch (Exception e) { log.error("sendMessageBack exception, group: " + this.consumerGroup + " msg: " + msg.toString(), e); @@ -432,7 +432,7 @@ public void run() { final Object objLock = messageQueueLock.fetchLockObject(this.messageQueue); synchronized (objLock) { if (MessageModel.BROADCASTING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel()) - || (this.processQueue.isLocked() && !this.processQueue.isLockExpired())) { + || this.processQueue.isLocked() && !this.processQueue.isLockExpired()) { final long beginTime = System.currentTimeMillis(); for (boolean continueConsume = true; continueConsume; ) { if (this.processQueue.isDropped()) { @@ -480,7 +480,7 @@ public void run() { consumeMessageContext.setMsgList(msgs); consumeMessageContext.setSuccess(false); // init the consume context type - consumeMessageContext.setProps(new HashMap()); + consumeMessageContext.setProps(new HashMap<>()); ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext); } @@ -498,7 +498,7 @@ public void run() { status = messageListener.consumeMessage(Collections.unmodifiableList(msgs), context); } catch (Throwable e) { log.warn(String.format("consumeMessage exception: %s Group: %s Msgs: %s MQ: %s", - RemotingHelper.exceptionSimpleDesc(e), + UtilAll.exceptionSimpleDesc(e), ConsumeMessageOrderlyService.this.consumerGroup, msgs, messageQueue), e); diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopConcurrentlyService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopConcurrentlyService.java new file mode 100644 index 00000000000..c2b39ad7bb3 --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopConcurrentlyService.java @@ -0,0 +1,483 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(String.format("consumeMessageDirectly exception: %s Group: %s Msgs: %s MQ: %s", + 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); + 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. isDropped={}, isPopTimeout={}, messageQueue={}, msgs={}", + processQueue.isDropped(), isPopTimeout(), messageQueue, msgs); + } + } + + public MessageQueue getMessageQueue() { + return messageQueue; + } + + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopOrderlyService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopOrderlyService.java new file mode 100644 index 00000000000..ae6adfea5df --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopOrderlyService.java @@ -0,0 +1,407 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(String.format("consumeMessageDirectly exception: %s Group: %s Msgs: %s MQ: %s", + 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; + } + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageService.java index 5078c978835..ee684730aed 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageService.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageService.java @@ -19,7 +19,7 @@ import java.util.List; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult; +import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult; public interface ConsumeMessageService { void start(); @@ -41,4 +41,9 @@ void submitConsumeRequest( final ProcessQueue processQueue, final MessageQueue messageQueue, final boolean dispathToConsume); + + void submitPopConsumeRequest( + final List msgs, + final PopProcessQueue processQueue, + final MessageQueue messageQueue); } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java index d5157501b2c..2d37581bbf1 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java @@ -33,8 +33,8 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; 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; @@ -54,35 +54,37 @@ 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.log.ClientLogger; 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.filter.FilterAPI; 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.protocol.NamespaceUtil; -import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.common.sysflag.PullSysFlag; -import org.apache.rocketmq.logging.InternalLogger; 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; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class DefaultLitePullConsumerImpl implements MQConsumerInner { - private final InternalLogger log = ClientLogger.getLog(); + 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 final ArrayList filterMessageHookList = new ArrayList<>(); private volatile ServiceState serviceState = ServiceState.CREATE_JUST; @@ -110,9 +112,13 @@ private enum SubscriptionType { */ private long pullTimeDelayMillsWhenException = 1000; /** - * Flow control interval + * Flow control interval when message cache is full */ - private static final long PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL = 50; + 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 */ @@ -120,22 +126,24 @@ private enum SubscriptionType { 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(); + new ConcurrentHashMap<>(); private AssignedMessageQueue assignedMessageQueue = new AssignedMessageQueue(); - private final BlockingQueue consumeRequestCache = new LinkedBlockingQueue(); + private final BlockingQueue consumeRequestCache = new LinkedBlockingQueue<>(); private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor; private final ScheduledExecutorService scheduledExecutorService; - private Map topicMessageQueueChangeListenerMap = new HashMap(); + private Map topicMessageQueueChangeListenerMap = new HashMap<>(); - private Map> messageQueuesForTopic = new HashMap>(); + private Map> messageQueuesForTopic = new HashMap<>(); private long consumeRequestFlowControlTimes = 0L; @@ -147,7 +155,11 @@ private enum SubscriptionType { private final MessageQueueLock messageQueueLock = new MessageQueueLock(); - private final ArrayList consumeMessageHookList = new ArrayList(); + 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; @@ -156,12 +168,7 @@ public DefaultLitePullConsumerImpl(final DefaultLitePullConsumer defaultLitePull this.defaultLitePullConsumer.getPullThreadNums(), new ThreadFactoryImpl("PullMsgThread-" + this.defaultLitePullConsumer.getConsumerGroup()) ); - this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return new Thread(r, "MonitorMessageQueueChangeThread"); - } - }); + this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("MonitorMessageQueueChangeThread")); this.pullTimeDelayMillsWhenException = defaultLitePullConsumer.getPullTimeDelayMillsWhenException(); } @@ -233,19 +240,23 @@ private void updatePullTask(String topic, Set mqNewSet) { class MessageQueueListenerImpl implements MessageQueueListener { @Override public void messageQueueChanged(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; - } + 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; } } @@ -379,7 +390,7 @@ private void operateAfterRunning() throws MQClientException { } // If assign function invoke before start function, then update pull task after initialization. if (subscriptionType == SubscriptionType.ASSIGN) { - updateAssignPullTask(assignedMessageQueue.messageQueues()); + updateAssignPullTask(assignedMessageQueue.getAssignedMessageQueues()); } for (String topic : topicMessageQueueChangeListenerMap.keySet()) { @@ -455,6 +466,9 @@ private void updateAssignPullTask(Collection mqNewSet) { } private void updateTopicSubscribeInfoWhenSubscriptionChanged() { + if (doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged) { + return; + } Map subTable = rebalanceImpl.getSubscriptionInner(); if (subTable != null) { for (final Map.Entry entry : subTable.entrySet()) { @@ -464,6 +478,42 @@ private void updateTopicSubscribeInfoWhenSubscriptionChanged() { } } + /** + * 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)) { @@ -524,6 +574,17 @@ public synchronized void assign(Collection 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) { @@ -561,6 +622,19 @@ public synchronized List poll(long timeout) { 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); + this.executeHookAfter(consumeMessageContext); + } + consumeRequest.getProcessQueue().setLastConsumeTimestamp(System.currentTimeMillis()); return messages; } } catch (InterruptedException ignore) { @@ -579,7 +653,7 @@ public void resume(Collection messageQueues) { } public synchronized void seek(MessageQueue messageQueue, long offset) throws MQClientException { - if (!assignedMessageQueue.messageQueues().contains(messageQueue)) { + 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 { @@ -645,21 +719,77 @@ private void removePullTask(final String topic) { } public synchronized void commitAll() { - try { - for (MessageQueue messageQueue : assignedMessageQueue.messageQueues()) { - long consumerOffset = assignedMessageQueue.getConsumerOffset(messageQueue); - if (consumerOffset != -1) { - ProcessQueue processQueue = assignedMessageQueue.getProcessQueue(messageQueue); - if (processQueue != null && !processQueue.isDropped()) { - updateConsumeOffset(messageQueue, consumerOffset); - } + 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 (defaultLitePullConsumer.getMessageModel() == MessageModel.BROADCASTING) { - offsetStore.persistAll(assignedMessageQueue.messageQueues()); + } + + 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); } - } catch (Exception e) { - log.error("An error occurred when update consume offset Automatically."); + } else { + log.error("consumerOffset is -1 in messageQueue [" + messageQueue + "]."); } } @@ -765,8 +895,10 @@ public void run() { return; } + processQueue.setLastPullTimestamp(System.currentTimeMillis()); + if ((long) consumeRequestCache.size() * defaultLitePullConsumer.getPullBatchSize() > defaultLitePullConsumer.getPullThresholdForAll()) { - scheduledThreadPoolExecutor.schedule(this, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL, TimeUnit.MILLISECONDS); + 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={}", consumeRequestCache.size(), consumeRequestFlowControlTimes); } @@ -777,7 +909,7 @@ public void run() { long cachedMessageSizeInMiB = processQueue.getMsgSize().get() / (1024 * 1024); if (cachedMessageCount > defaultLitePullConsumer.getPullThresholdForQueue()) { - scheduledThreadPoolExecutor.schedule(this, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL, TimeUnit.MILLISECONDS); + 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={}", @@ -787,7 +919,7 @@ public void run() { } if (cachedMessageSizeInMiB > defaultLitePullConsumer.getPullThresholdSizeForQueue()) { - scheduledThreadPoolExecutor.schedule(this, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL, TimeUnit.MILLISECONDS); + 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={}", @@ -797,7 +929,7 @@ public void run() { } if (processQueue.getMaxSpan() > defaultLitePullConsumer.getConsumeMaxSpan()) { - scheduledThreadPoolExecutor.schedule(this, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL, TimeUnit.MILLISECONDS); + 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={}", @@ -825,7 +957,9 @@ public void run() { if (subscriptionType == SubscriptionType.SUBSCRIBE) { subscriptionData = rebalanceImpl.getSubscriptionInner().get(topic); } else { - subscriptionData = FilterAPI.buildSubscriptionData(topic, SubscriptionData.SUB_ALL); + 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()); @@ -852,7 +986,11 @@ public void run() { } catch (InterruptedException interruptedException) { log.warn("Polling thread was interrupted.", interruptedException); } catch (Throwable e) { - pullDelayTimeMills = pullTimeDelayMillsWhenException; + 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); } @@ -924,18 +1062,6 @@ private PullResult pullSyncImpl(MessageQueue mq, SubscriptionData subscriptionDa null ); this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData); - if (!this.consumeMessageHookList.isEmpty()) { - ConsumeMessageContext consumeMessageContext = new ConsumeMessageContext(); - consumeMessageContext.setNamespace(defaultLitePullConsumer.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); - this.executeHookAfter(consumeMessageContext); - } return pullResult; } @@ -980,7 +1106,7 @@ public ConsumeFromWhere consumeFromWhere() { @Override public Set subscriptions() { - Set subSet = new HashSet(); + Set subSet = new HashSet<>(); subSet.addAll(this.rebalanceImpl.getSubscriptionInner().values()); @@ -998,7 +1124,7 @@ public void doRebalance() { public void persistConsumerOffset() { try { checkServiceState(); - Set mqs = new HashSet(); + Set mqs = new HashSet<>(); if (this.subscriptionType == SubscriptionType.SUBSCRIBE) { Set allocateMq = this.rebalanceImpl.getProcessQueueTable().keySet(); mqs.addAll(allocateMq); @@ -1048,6 +1174,15 @@ public ConsumerRunningInfo consumerRunningInfo() { 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; } @@ -1091,12 +1226,11 @@ private boolean isSetEqual(Set set1, Set set2) { return true; } - if (set1 == null || set2 == null || set1.size() != set2.size() - || set1.size() == 0 || set2.size() == 0) { + if (set1 == null || set2 == null || set1.size() != set2.size() || set1.size() == 0) { return false; } - Iterator iter = set2.iterator(); + Iterator iter = set2.iterator(); boolean isEqual = true; while (iter.hasNext()) { if (!set1.contains(iter.next())) { @@ -1106,6 +1240,10 @@ private boolean isSetEqual(Set set1, Set set2) { return isEqual; } + public AssignedMessageQueue getAssignedMessageQueue() { + return assignedMessageQueue; + } + public synchronized void registerTopicMessageQueueChangeListener(String topic, TopicMessageQueueChangeListener listener) throws MQClientException { if (topic == null || listener == null) { @@ -1122,7 +1260,7 @@ public synchronized void registerTopicMessageQueueChangeListener(String topic, } private Set parseMessageQueues(Set queueSet) { - Set resultQueues = new HashSet(); + Set resultQueues = new HashSet<>(); for (MessageQueue messageQueue : queueSet) { String userTopic = NamespaceUtil.withoutNamespace(messageQueue.getTopic(), this.defaultLitePullConsumer.getNamespace()); diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPullConsumerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPullConsumerImpl.java index eed5fa43f04..3348f319277 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPullConsumerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPullConsumerImpl.java @@ -42,29 +42,29 @@ 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.log.ClientLogger; 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.filter.FilterAPI; import org.apache.rocketmq.common.help.FAQUrl; -import org.apache.rocketmq.common.protocol.NamespaceUtil; -import org.apache.rocketmq.logging.InternalLogger; 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.protocol.body.ConsumerRunningInfo; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; 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.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; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * This class will be removed in 2022, and a better implementation {@link DefaultLitePullConsumerImpl} is recommend to use @@ -72,12 +72,12 @@ */ @Deprecated public class DefaultMQPullConsumerImpl implements MQConsumerInner { - private final InternalLogger log = ClientLogger.getLog(); + 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 final ArrayList consumeMessageHookList = new ArrayList<>(); + private final ArrayList filterMessageHookList = new ArrayList<>(); private volatile ServiceState serviceState = ServiceState.CREATE_JUST; protected MQClientInstance mQClientFactory; private PullAPIWrapper pullAPIWrapper; @@ -100,7 +100,7 @@ public void createTopic(String key, String newTopic, int queueNum) throws MQClie public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag) throws MQClientException { this.isRunning(); - this.mQClientFactory.getMQAdminImpl().createTopic(key, newTopic, queueNum, topicSysFlag); + this.mQClientFactory.getMQAdminImpl().createTopic(key, newTopic, queueNum, topicSysFlag, null); } private void isRunning() throws MQClientException { @@ -124,7 +124,7 @@ public Set fetchMessageQueuesInBalance(String topic) throws MQClie } ConcurrentMap mqTable = this.rebalanceImpl.getProcessQueueTable(); - Set mqResult = new HashSet(); + Set mqResult = new HashSet<>(); for (MessageQueue mq : mqTable.keySet()) { if (mq.getTopic().equals(topic)) { mqResult.add(mq); @@ -151,7 +151,7 @@ public Set fetchSubscribeMessageQueues(String topic) throws MQClie } public Set parseSubscribeMessageQueues(Set queueSet) { - Set resultQueues = new HashSet(); + Set resultQueues = new HashSet<>(); for (MessageQueue messageQueue : queueSet) { String userTopic = NamespaceUtil.withoutNamespace(messageQueue.getTopic(), this.defaultMQPullConsumer.getNamespace()); @@ -355,7 +355,7 @@ public ConsumeFromWhere consumeFromWhere() { @Override public Set subscriptions() { - Set result = new HashSet(); + Set result = new HashSet<>(); Set topics = this.defaultMQPullConsumer.getRegisterTopics(); if (topics != null) { @@ -367,8 +367,10 @@ public Set subscriptions() { } catch (Exception e) { log.error("parse subscription error", e); } - ms.setSubVersion(0L); - result.add(ms); + if (ms != null) { + ms.setSubVersion(0L); + result.add(ms); + } } } } @@ -387,7 +389,7 @@ public void doRebalance() { public void persistConsumerOffset() { try { this.isRunning(); - Set mqs = new HashSet(); + Set mqs = new HashSet<>(); Set allocateMq = this.rebalanceImpl.getProcessQueueTable().keySet(); mqs.addAll(allocateMq); this.offsetStore.persistAll(mqs); @@ -447,6 +449,13 @@ public void pull(MessageQueue mq, String subExpression, long offset, int maxNums 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 { @@ -466,6 +475,7 @@ private void pullAsyncImpl( 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 { @@ -483,6 +493,11 @@ private void pullAsyncImpl( 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); } @@ -502,6 +517,7 @@ private void pullAsyncImpl( isTagType ? 0L : subscriptionData.getSubVersion(), offset, maxNums, + maxSizeInBytes, sysFlag, 0, this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis(), @@ -526,6 +542,26 @@ public void onException(Throwable 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); @@ -571,18 +607,23 @@ public void updateConsumeOffsetToBroker(MessageQueue mq, long offset, boolean is 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 brokerAddr = (null != brokerName) ? this.mQClientFactory.findBrokerAddressInPublish(brokerName) + 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(consumerGroup)) { consumerGroup = this.defaultMQPullConsumer.getConsumerGroup(); } - this.mQClientFactory.getMQClientAPIImpl().consumerSendMessageBack(brokerAddr, msg, consumerGroup, delayLevel, 3000, - this.defaultMQPullConsumer.getMaxReconsumeTimes()); + this.mQClientFactory.getMQClientAPIImpl().consumerSendMessageBack(brokerAddr, brokerName, msg, consumerGroup, + delayLevel, 3000, this.defaultMQPullConsumer.getMaxReconsumeTimes()); } catch (Exception e) { log.error("sendMessageBack Exception, " + this.defaultMQPullConsumer.getConsumerGroup(), e); diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java index 59b8deb3fba..e57579321cb 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java @@ -27,12 +27,18 @@ 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.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; @@ -46,38 +52,46 @@ 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.log.ClientLogger; 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.filter.FilterAPI; import org.apache.rocketmq.common.help.FAQUrl; -import org.apache.rocketmq.common.protocol.NamespaceUtil; -import org.apache.rocketmq.logging.InternalLogger; 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.protocol.body.ConsumeStatus; -import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo; -import org.apache.rocketmq.common.protocol.body.ProcessQueueInfo; -import org.apache.rocketmq.common.protocol.body.QueueTimeSpan; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; 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 { /** @@ -85,21 +99,25 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner { */ private long pullTimeDelayMillsWhenException = 3000; /** - * Flow control interval + * 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_FLOW_CONTROL = 50; + 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 final InternalLogger log = ClientLogger.getLog(); + 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 ArrayList filterMessageHookList = new ArrayList<>(); private final long consumerStartTimestamp = System.currentTimeMillis(); - private final ArrayList consumeMessageHookList = new ArrayList(); + private final ArrayList consumeMessageHookList = new ArrayList<>(); private final RPCHook rpcHook; private volatile ServiceState serviceState = ServiceState.CREATE_JUST; private MQClientInstance mQClientFactory; @@ -109,9 +127,21 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner { 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 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; @@ -138,6 +168,7 @@ public void executeHookBefore(final ConsumeMessageContext context) { try { hook.consumeMessageBefore(context); } catch (Throwable e) { + log.warn("consumeMessageHook {} executeHookBefore exception", hook.hookName(), e); } } } @@ -149,6 +180,7 @@ public void executeHookAfter(final ConsumeMessageContext context) { try { hook.consumeMessageAfter(context); } catch (Throwable e) { + log.warn("consumeMessageHook {} executeHookAfter exception", hook.hookName(), e); } } } @@ -159,7 +191,7 @@ public void createTopic(String key, String newTopic, int queueNum) throws MQClie } public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag) throws MQClientException { - this.mQClientFactory.getMQAdminImpl().createTopic(key, newTopic, queueNum, topicSysFlag); + this.mQClientFactory.getMQAdminImpl().createTopic(key, newTopic, queueNum, topicSysFlag, null); } public Set fetchSubscribeMessageQueues(String topic) throws MQClientException { @@ -177,7 +209,7 @@ public Set fetchSubscribeMessageQueues(String topic) throws MQClie } public Set parseSubscribeMessageQueues(Set messageQueueList) { - Set resultQueues = new HashSet(); + 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())); @@ -237,7 +269,7 @@ public void pullMessage(final PullRequest pullRequest) { long cachedMessageSizeInMiB = processQueue.getMsgSize().get() / (1024 * 1024); if (cachedMessageCount > this.defaultMQPushConsumer.getPullThresholdForQueue()) { - this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL); + 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={}", @@ -247,7 +279,7 @@ public void pullMessage(final PullRequest pullRequest) { } if (cachedMessageSizeInMiB > this.defaultMQPushConsumer.getPullThresholdSizeForQueue()) { - this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL); + 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={}", @@ -258,7 +290,7 @@ public void pullMessage(final PullRequest pullRequest) { if (!this.consumeOrderly) { if (processQueue.getMaxSpan() > this.defaultMQPushConsumer.getConsumeConcurrentlyMaxSpan()) { - this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL); + 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={}", @@ -273,6 +305,9 @@ public void pullMessage(final PullRequest pullRequest) { 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); @@ -399,7 +434,11 @@ public void onException(Throwable e) { log.warn("execute the pull request exception", e); } - DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); + 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); + } } }; @@ -437,6 +476,7 @@ public void onException(Throwable e) { subscriptionData.getSubVersion(), pullRequest.getNextOffset(), this.defaultMQPushConsumer.getPullBatchSize(), + this.defaultMQPushConsumer.getPullBatchSizeInBytes(), sysFlag, commitOffsetValue, BROKER_SUSPEND_MAX_TIME_MILLIS, @@ -450,6 +490,173 @@ public void onException(Throwable e) { } } + 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("pullMessage 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: + DefaultMQPushConsumerImpl.this.executePopPullRequestLater(popRequest, pullTimeDelayMillsWhenException); + break; + 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 = msgFoundList; + if (!subscriptionData.getTagsSet().isEmpty() && !subscriptionData.isClassFilterMode() + && popResult.getMsgFoundList().size() > 0) { + msgListFilterAgain = new ArrayList<>(popResult.getMsgFoundList().size()); + for (MessageExt msg : popResult.getMsgFoundList()) { + if (msg.getTags() != null) { + if (subscriptionData.getTagsSet().contains(msg.getTags())) { + msgListFilterAgain.add(msg); + } + } + } + } + + 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()); + } + } + } + } + + 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, " @@ -459,7 +666,7 @@ private void makeSureStateOK() throws MQClientException { } } - private void executePullRequestLater(final PullRequest pullRequest, final long timeDelay) { + void executePullRequestLater(final PullRequest pullRequest, final long timeDelay) { this.mQClientFactory.getPullMessageService().executePullRequestLater(pullRequest, timeDelay); } @@ -479,6 +686,14 @@ 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); @@ -509,36 +724,147 @@ public void resume() { 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, null, mq); + } + + + private void sendMessageBack(MessageExt msg, int delayLevel, final String brokerName, final MessageQueue mq) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { + boolean needRetry = true; try { - String brokerAddr = (null != brokerName) ? this.mQClientFactory.findBrokerAddressInPublish(brokerName) - : RemotingHelper.parseSocketAddressAddr(msg.getStoreHost()); - this.mQClientFactory.getMQClientAPIImpl().consumerSendMessageBack(brokerAddr, msg, - this.defaultMQPushConsumer.getConsumerGroup(), delayLevel, 5000, getMaxReconsumeTimes()); - } catch (Exception e) { - log.error("sendMessageBack Exception, " + this.defaultMQPushConsumer.getConsumerGroup(), e); + 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()); + 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())); + } + } - Message newMsg = new Message(MixAll.getRetryTopic(this.defaultMQPushConsumer.getConsumerGroup()), msg.getBody()); + private void sendMessageBackAsNormalMessage(MessageExt msg) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { + Message newMsg = new Message(MixAll.getRetryTopic(this.defaultMQPushConsumer.getConsumerGroup()), msg.getBody()); - String originMsgId = MessageAccessor.getOriginMessageId(msg); - MessageAccessor.setOriginMessageId(newMsg, UtilAll.isBlank(originMsgId) ? msg.getMsgId() : originMsgId); + 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(getMaxReconsumeTimes())); - MessageAccessor.clearProperty(newMsg, MessageConst.PROPERTY_TRANSACTION_PREPARED); - newMsg.setDelayTimeLevel(3 + msg.getReconsumeTimes()); + 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(getMaxReconsumeTimes())); + MessageAccessor.clearProperty(newMsg, MessageConst.PROPERTY_TRANSACTION_PREPARED); + newMsg.setDelayTimeLevel(3 + msg.getReconsumeTimes()); - this.mQClientFactory.getDefaultMQProducer().send(newMsg); - } finally { - msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQPushConsumer.getNamespace())); + 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.setBname(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); } } - private int getMaxReconsumeTimes() { + 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.setBname(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; @@ -593,9 +919,11 @@ public synchronized void start() throws MQClientException { this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPushConsumer.getAllocateMessageQueueStrategy()); this.rebalanceImpl.setmQClientFactory(this.mQClientFactory); - this.pullAPIWrapper = new PullAPIWrapper( - mQClientFactory, - this.defaultMQPushConsumer.getConsumerGroup(), isUnitMode()); + if (this.pullAPIWrapper == null) { + this.pullAPIWrapper = new PullAPIWrapper( + mQClientFactory, + this.defaultMQPushConsumer.getConsumerGroup(), isUnitMode()); + } this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList); if (this.defaultMQPushConsumer.getOffsetStore() != null) { @@ -619,13 +947,20 @@ public synchronized void start() throws MQClientException { 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) { @@ -825,6 +1160,23 @@ private void checkConfig() throws MQClientException { + 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 { @@ -864,6 +1216,9 @@ public MessageListener getMessageListenerInner() { } private void updateTopicSubscribeInfoWhenSubscriptionChanged() { + if (doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged) { + return; + } Map subTable = this.getSubscriptionInner(); if (subTable != null) { for (final Map.Entry entry : subTable.entrySet()) { @@ -891,7 +1246,7 @@ public void subscribe(String topic, String subExpression) throws MQClientExcepti public void subscribe(String topic, String fullClassName, String filterClassSource) throws MQClientException { try { - SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, "*"); + SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, SubscriptionData.SUB_ALL); subscriptionData.setSubString(fullClassName); subscriptionData.setClassFilterMode(true); subscriptionData.setFilterClassSource(filterClassSource); @@ -958,12 +1313,11 @@ public void setConsumeOrderly(boolean consumeOrderly) { this.consumeOrderly = consumeOrderly; } - public void resetOffsetByTimeStamp(long timeStamp) - throws RemotingException, MQBrokerException, InterruptedException, MQClientException { + public void resetOffsetByTimeStamp(long timeStamp) throws MQClientException { for (String topic : rebalanceImpl.getSubscriptionInner().keySet()) { Set mqs = rebalanceImpl.getTopicSubscribeInfoTable().get(topic); - Map offsetTable = new HashMap(); - if (mqs != null) { + if (CollectionUtils.isNotEmpty(mqs)) { + Map offsetTable = new HashMap<>(mqs.size(), 1); for (MessageQueue mq : mqs) { long offset = searchOffset(mq, timeStamp); offsetTable.put(mq, offset); @@ -999,11 +1353,7 @@ public ConsumeFromWhere consumeFromWhere() { @Override public Set subscriptions() { - Set subSet = new HashSet(); - - subSet.addAll(this.rebalanceImpl.getSubscriptionInner().values()); - - return subSet; + return new HashSet<>(this.rebalanceImpl.getSubscriptionInner().values()); } @Override @@ -1017,7 +1367,7 @@ public void doRebalance() { public void persistConsumerOffset() { try { this.makeSureStateOK(); - Set mqs = new HashSet(); + Set mqs = new HashSet<>(); Set allocateMq = this.rebalanceImpl.getProcessQueueTable().keySet(); mqs.addAll(allocateMq); @@ -1081,6 +1431,17 @@ public ConsumerRunningInfo consumerRunningInfo() { 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); @@ -1139,7 +1500,7 @@ private long computeAccumulationTotal() { public List queryConsumeTimeSpan(final String topic) throws RemotingException, MQClientException, InterruptedException, MQBrokerException { - List queueTimeSpan = new ArrayList(); + List queueTimeSpan = new ArrayList<>(); TopicRouteData routeData = this.mQClientFactory.getMQClientAPIImpl().getTopicRouteInfoFromNameServer(topic, 3000); for (BrokerData brokerData : routeData.getBrokerDatas()) { String addr = brokerData.selectBrokerAddr(); @@ -1149,6 +1510,19 @@ public List queryConsumeTimeSpan(final String topic) 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) { @@ -1175,4 +1549,8 @@ public void setConsumeMessageService(ConsumeMessageService consumeMessageService public void setPullTimeDelayMillsWhenException(long pullTimeDelayMillsWhenException) { this.pullTimeDelayMillsWhenException = pullTimeDelayMillsWhenException; } + + int[] getPopDelayLevel() { + return popDelayLevel; + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MQConsumerInner.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MQConsumerInner.java index c2e8a1dfc49..7e84b508b1c 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MQConsumerInner.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MQConsumerInner.java @@ -19,10 +19,10 @@ import java.util.Set; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; +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 diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageQueueLock.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageQueueLock.java index a02f1b6ef6e..0fc9c93b449 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageQueueLock.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageQueueLock.java @@ -24,19 +24,32 @@ * Message lock,strictly ensure the single queue only one thread at a time consuming */ public class MessageQueueLock { - private ConcurrentMap mqLockTable = - new ConcurrentHashMap(); + private ConcurrentMap> mqLockTable = + new ConcurrentHashMap<>(32); public Object fetchLockObject(final MessageQueue mq) { - Object objLock = this.mqLockTable.get(mq); - if (null == objLock) { - objLock = new Object(); - Object prevLock = this.mqLockTable.putIfAbsent(mq, objLock); + 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) { - objLock = prevLock; + lock = prevLock; } } - return objLock; + return lock; } } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageRequest.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageRequest.java new file mode 100644 index 00000000000..a808538b019 --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageRequest.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); +} diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopProcessQueue.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopProcessQueue.java new file mode 100644 index 00000000000..3b39b86cc71 --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopProcessQueue.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + 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 + "]"; + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopRequest.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopRequest.java new file mode 100644 index 00000000000..c47f2d020e7 --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopRequest.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ProcessQueue.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ProcessQueue.java index ba00aaef994..ab94a984677 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ProcessQueue.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ProcessQueue.java @@ -26,16 +26,14 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; - import org.apache.commons.lang3.StringUtils; - import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; -import org.apache.rocketmq.client.log.ClientLogger; -import org.apache.rocketmq.logging.InternalLogger; 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.protocol.body.ProcessQueueInfo; +import org.apache.rocketmq.remoting.protocol.body.ProcessQueueInfo; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * Queue consumption snapshot @@ -45,16 +43,16 @@ public class ProcessQueue { 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 InternalLogger log = ClientLogger.getLog(); + private final Logger log = LoggerFactory.getLogger(ProcessQueue.class); private final ReadWriteLock treeMapLock = new ReentrantReadWriteLock(); - private final TreeMap msgTreeMap = new TreeMap(); + private final TreeMap msgTreeMap = new TreeMap<>(); private final AtomicLong msgCount = new AtomicLong(); private final AtomicLong msgSize = new AtomicLong(); private final Lock consumeLock = new ReentrantLock(); /** * A subset of msgTreeMap, will only be used when orderly consume */ - private final TreeMap consumingMsgOrderlyTreeMap = new TreeMap(); + private final TreeMap consumingMsgOrderlyTreeMap = new TreeMap<>(); private final AtomicLong tryUnlockTimes = new AtomicLong(0); private volatile long queueOffsetMax = 0L; private volatile boolean dropped = false; @@ -77,11 +75,11 @@ public boolean isPullExpired() { * @param pushConsumer */ public void cleanExpiredMsg(DefaultMQPushConsumer pushConsumer) { - if (pushConsumer.getDefaultMQPushConsumerImpl().isConsumeOrderly()) { + if (pushConsumer.isConsumeOrderly()) { return; } - int loop = msgTreeMap.size() < 16 ? msgTreeMap.size() : 16; + int loop = Math.min(msgTreeMap.size(), 16); for (int i = 0; i < loop; i++) { MessageExt msg = null; try { @@ -91,11 +89,7 @@ public void cleanExpiredMsg(DefaultMQPushConsumer pushConsumer) { String consumeStartTimeStamp = MessageAccessor.getConsumeStartTimeStamp(msgTreeMap.firstEntry().getValue()); if (StringUtils.isNotEmpty(consumeStartTimeStamp) && System.currentTimeMillis() - Long.parseLong(consumeStartTimeStamp) > pushConsumer.getConsumeTimeout() * 60 * 1000) { msg = msgTreeMap.firstEntry().getValue(); - } else { - break; } - } else { - break; } } finally { this.treeMapLock.readLock().unlock(); @@ -104,6 +98,10 @@ public void cleanExpiredMsg(DefaultMQPushConsumer pushConsumer) { log.error("getExpiredMsg exception", e); } + if (msg == null) { + break; + } + try { pushConsumer.sendMessageBack(msg, 3); @@ -303,7 +301,7 @@ public void makeMessageToConsumeAgain(List msgs) { } public List takeMessages(final int batchSize) { - List result = new ArrayList(batchSize); + List result = new ArrayList<>(batchSize); final long now = System.currentTimeMillis(); try { this.treeMapLock.writeLock().lockInterruptibly(); @@ -334,6 +332,27 @@ public List takeMessages(final int batchSize) { 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(); diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullAPIWrapper.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullAPIWrapper.java index cc42a9e830e..5180d376ea4 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullAPIWrapper.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullAPIWrapper.java @@ -23,6 +23,7 @@ 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; @@ -33,33 +34,35 @@ 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.client.log.ClientLogger; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.filter.ExpressionType; -import org.apache.rocketmq.logging.InternalLogger; 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.protocol.header.PullMessageRequestHeader; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; +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 final InternalLogger log = ClientLogger.getLog(); + 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); + new ConcurrentHashMap<>(32); private volatile boolean connectBrokerByUser = false; private volatile long defaultBrokerId = MixAll.MASTER_ID; - private Random random = new Random(System.currentTimeMillis()); - private ArrayList filterMessageHookList = new ArrayList(); + private Random random = new Random(System.nanoTime()); + private ArrayList filterMessageHookList = new ArrayList<>(); public PullAPIWrapper(MQClientInstance mQClientFactory, String consumerGroup, boolean unitMode) { this.mQClientFactory = mQClientFactory; @@ -74,11 +77,41 @@ public PullResult processPullResult(final MessageQueue mq, final PullResult pull this.updatePullFromWhichNode(mq, pullResultExt.getSuggestWhichBrokerId()); if (PullStatus.FOUND == pullResult.getPullStatus()) { ByteBuffer byteBuffer = ByteBuffer.wrap(pullResultExt.getMessageBinary()); - List msgList = MessageDecoder.decodes(byteBuffer); + 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()); + msgListFilterAgain = new ArrayList<>(msgList.size()); for (MessageExt msg : msgList) { if (msg.getTags() != null) { if (subscriptionData.getTagsSet().contains(msg.getTags())) { @@ -105,6 +138,10 @@ public PullResult processPullResult(final MessageQueue mq, final PullResult pull 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); @@ -147,6 +184,7 @@ public PullResult pullKernelImpl( final long subVersion, final long offset, final int maxNums, + final int maxSizeInBytes, final int sysFlag, final long commitOffset, final long brokerSuspendMaxTimeMillis, @@ -155,15 +193,16 @@ public PullResult pullKernelImpl( final PullCallback pullCallback ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { FindBrokerResult findBrokerResult = - this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), + this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), this.recalculatePullFromWhichNode(mq), false); if (null == findBrokerResult) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); findBrokerResult = - this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), + this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), this.recalculatePullFromWhichNode(mq), false); } + if (findBrokerResult != null) { { // check version @@ -190,7 +229,9 @@ public PullResult pullKernelImpl( requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis); requestHeader.setSubscription(subExpression); requestHeader.setSubVersion(subVersion); + requestHeader.setMaxMsgBytes(maxSizeInBytes); requestHeader.setExpressionType(expressionType); + requestHeader.setBname(mq.getBrokerName()); String brokerAddr = findBrokerResult.getBrokerAddr(); if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) { @@ -210,6 +251,36 @@ public PullResult pullKernelImpl( 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; @@ -269,4 +340,57 @@ public long getDefaultBrokerId() { 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.setBname(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); + } + } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullMessageService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullMessageService.java index bd46a58859a..b5e6f9f7900 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullMessageService.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullMessageService.java @@ -19,25 +19,22 @@ import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.client.impl.factory.MQClientInstance; -import org.apache.rocketmq.client.log.ClientLogger; import org.apache.rocketmq.common.ServiceThread; -import org.apache.rocketmq.logging.InternalLogger; +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 InternalLogger log = ClientLogger.getLog(); - private final LinkedBlockingQueue pullRequestQueue = new LinkedBlockingQueue(); + 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 ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return new Thread(r, "PullMessageServiceScheduledThread"); - } - }); + .newSingleThreadScheduledExecutor(new ThreadFactoryImpl("PullMessageServiceScheduledThread")); public PullMessageService(MQClientInstance mQClientFactory) { this.mQClientFactory = mQClientFactory; @@ -52,15 +49,36 @@ public void run() { } }, timeDelay, TimeUnit.MILLISECONDS); } else { - log.warn("PullMessageServiceScheduledThread has shutdown"); + logger.warn("PullMessageServiceScheduledThread has shutdown"); } } public void executePullRequestImmediately(final PullRequest pullRequest) { try { - this.pullRequestQueue.put(pullRequest); + 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) { - log.error("executePullRequestImmediately pullRequestQueue.put", e); + logger.error("executePullRequestImmediately pullRequestQueue.put", e); } } @@ -68,7 +86,7 @@ public void executeTaskLater(final Runnable r, final long timeDelay) { if (!isStopped()) { this.scheduledExecutorService.schedule(r, timeDelay, TimeUnit.MILLISECONDS); } else { - log.warn("PullMessageServiceScheduledThread has shutdown"); + logger.warn("PullMessageServiceScheduledThread has shutdown"); } } @@ -82,25 +100,39 @@ private void pullMessage(final PullRequest pullRequest) { DefaultMQPushConsumerImpl impl = (DefaultMQPushConsumerImpl) consumer; impl.pullMessage(pullRequest); } else { - log.warn("No matched consumer for the PullRequest {}, drop it", pullRequest); + 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() { - log.info(this.getServiceName() + " service started"); + logger.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { - PullRequest pullRequest = this.pullRequestQueue.take(); - this.pullMessage(pullRequest); + MessageRequest messageRequest = this.messageRequestQueue.take(); + if (messageRequest.getMessageRequestMode() == MessageRequestMode.POP) { + this.popMessage((PopRequest) messageRequest); + } else { + this.pullMessage((PullRequest) messageRequest); + } } catch (InterruptedException ignored) { } catch (Exception e) { - log.error("Pull Message Service Run Method exception", e); + logger.error("Pull Message Service Run Method exception", e); } } - log.info(this.getServiceName() + " service end"); + logger.info(this.getServiceName() + " service end"); } @Override diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java index bf03ec38c3a..b90192b9925 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java @@ -17,8 +17,9 @@ package org.apache.rocketmq.client.impl.consumer; import org.apache.rocketmq.common.message.MessageQueue; +import org.apache.rocketmq.common.message.MessageRequestMode; -public class PullRequest { +public class PullRequest implements MessageRequest { private String consumerGroup; private MessageQueue messageQueue; private ProcessQueue processQueue; @@ -101,4 +102,9 @@ public ProcessQueue getProcessQueue() { public void setProcessQueue(ProcessQueue processQueue) { this.processQueue = processQueue; } + + @Override + public MessageRequestMode getMessageRequestMode() { + return MessageRequestMode.PULL; + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullResultExt.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullResultExt.java index c34a68f9ab3..45538eda8d3 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullResultExt.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullResultExt.java @@ -25,11 +25,23 @@ 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() { diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java index 7677d8b685f..97d9460f827 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java @@ -31,27 +31,40 @@ 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.client.log.ClientLogger; +import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; -import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.body.LockBatchRequestBody; -import org.apache.rocketmq.common.protocol.body.UnlockBatchRequestBody; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; +import org.apache.rocketmq.common.message.MessageQueueAssignment; +import org.apache.rocketmq.common.message.MessageRequestMode; +import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; +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 InternalLogger log = ClientLogger.getLog(); - protected final ConcurrentMap processQueueTable = new ConcurrentHashMap(64); + 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>(); + new ConcurrentHashMap<>(); protected final ConcurrentMap subscriptionInner = - new ConcurrentHashMap(); + new ConcurrentHashMap<>(); protected String consumerGroup; protected MessageModel messageModel; protected AllocateMessageQueueStrategy allocateMessageQueueStrategy; protected MQClientInstance mQClientFactory; + private static final int TIMEOUT_CHECK_TIMES = 3; + private static final int QUERY_ASSIGNMENT_TIMEOUT = 3000; + + private Map topicBrokerRebalance = new ConcurrentHashMap<>(); + private Map topicClientRebalance = new ConcurrentHashMap<>(); public RebalanceImpl(String consumerGroup, MessageModel messageModel, AllocateMessageQueueStrategy allocateMessageQueueStrategy, @@ -63,7 +76,7 @@ public RebalanceImpl(String consumerGroup, MessageModel messageModel, } public void unlock(final MessageQueue mq, final boolean oneway) { - FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), MixAll.MASTER_ID, true); + FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), MixAll.MASTER_ID, true); if (findBrokerResult != null) { UnlockBatchRequestBody requestBody = new UnlockBatchRequestBody(); requestBody.setConsumerGroup(this.consumerGroup); @@ -89,8 +102,9 @@ public void unlockAll(final boolean oneway) { final String brokerName = entry.getKey(); final Set mqs = entry.getValue(); - if (mqs.isEmpty()) + if (mqs.isEmpty()) { continue; + } FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, true); if (findBrokerResult != null) { @@ -117,11 +131,20 @@ public void unlockAll(final boolean oneway) { } private HashMap> buildProcessQueueTableByBrokerName() { - HashMap> result = new HashMap>(); - for (MessageQueue mq : this.processQueueTable.keySet()) { - Set mqs = result.get(mq.getBrokerName()); + 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(); + mqs = new HashSet<>(); result.put(mq.getBrokerName(), mqs); } @@ -132,7 +155,7 @@ public void unlockAll(final boolean oneway) { } public boolean lock(final MessageQueue mq) { - FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), MixAll.MASTER_ID, true); + FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq), MixAll.MASTER_ID, true); if (findBrokerResult != null) { LockBatchRequestBody requestBody = new LockBatchRequestBody(); requestBody.setConsumerGroup(this.consumerGroup); @@ -151,10 +174,7 @@ public boolean lock(final MessageQueue mq) { } boolean lockOK = lockedMq.contains(mq); - log.info("the message queue lock {}, {} {}", - lockOK ? "OK" : "Failed", - this.consumerGroup, - mq); + log.info("message queue lock {}, {} {}", lockOK ? "OK" : "Failed", this.consumerGroup, mq); return lockOK; } catch (Exception e) { log.error("lockBatchMQ exception, " + mq, e); @@ -173,8 +193,9 @@ public void lockAll() { final String brokerName = entry.getKey(); final Set mqs = entry.getValue(); - if (mqs.isEmpty()) + if (mqs.isEmpty()) { continue; + } FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, true); if (findBrokerResult != null) { @@ -187,21 +208,16 @@ public void lockAll() { Set lockOKMQSet = this.mQClientFactory.getMQClientAPIImpl().lockBatchMQ(findBrokerResult.getBrokerAddr(), requestBody, 1000); - for (MessageQueue mq : lockOKMQSet) { + for (MessageQueue mq : mqs) { ProcessQueue processQueue = this.processQueueTable.get(mq); if (processQueue != null) { - if (!processQueue.isLocked()) { - log.info("the message queue locked OK, Group: {} {}", this.consumerGroup, mq); - } - - processQueue.setLocked(true); - processQueue.setLastLockTimestamp(System.currentTimeMillis()); - } - } - for (MessageQueue mq : mqs) { - if (!lockOKMQSet.contains(mq)) { - 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); } @@ -214,29 +230,74 @@ public void lockAll() { } } - public void doRebalance(final boolean isOrder) { + 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 { - this.rebalanceByTopic(topic, isOrder); + if (!clientRebalance(topic) && tryQueryAssignment(topic)) { + balanced = this.getRebalanceResultFromBroker(topic, isOrder); + } else { + balanced = this.rebalanceByTopic(topic, isOrder); + } } catch (Throwable e) { if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { - log.warn("rebalanceByTopic Exception", e); + log.warn("rebalance Exception", e); + balanced = false; } } } } this.truncateMessageQueueNotMyTopic(); + + return balanced; + } + + private boolean tryQueryAssignment(String topic) { + if (topicClientRebalance.containsKey(topic)) { + return false; + } + + if (topicBrokerRebalance.containsKey(topic)) { + return true; + } + String strategyName = allocateMessageQueueStrategy != null ? allocateMessageQueueStrategy.getName() : null; + int retryTimes = 0; + while (retryTimes++ < TIMEOUT_CHECK_TIMES) { + try { + Set resultSet = mQClientFactory.queryAssignment(topic, consumerGroup, + strategyName, messageModel, QUERY_ASSIGNMENT_TIMEOUT / TIMEOUT_CHECK_TIMES * retryTimes); + topicBrokerRebalance.put(topic, topic); + return true; + } catch (Throwable t) { + if (!(t instanceof RemotingTimeoutException)) { + log.error("tryQueryAssignment error.", t); + topicClientRebalance.put(topic, topic); + return false; + } + } + } + if (retryTimes >= TIMEOUT_CHECK_TIMES) { + // if never success before and timeout exceed TIMEOUT_CHECK_TIMES, force client rebalance + topicClientRebalance.put(topic, topic); + return false; + } + return true; } public ConcurrentMap getSubscriptionInner() { return subscriptionInner; } - private void rebalanceByTopic(final String topic, final boolean isOrder) { + private boolean rebalanceByTopic(final String topic, final boolean isOrder) { + boolean balanced = true; switch (messageModel) { case BROADCASTING: { Set mqSet = this.topicSubscribeInfoTable.get(topic); @@ -244,13 +305,12 @@ private void rebalanceByTopic(final String topic, final boolean isOrder) { boolean changed = this.updateProcessQueueTableInRebalance(topic, mqSet, isOrder); if (changed) { this.messageQueueChanged(topic, mqSet, mqSet); - log.info("messageQueueChanged {} {} {} {}", - consumerGroup, - 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; @@ -260,6 +320,7 @@ private void rebalanceByTopic(final String topic, final boolean isOrder) { List cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup); if (null == mqSet) { 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); } } @@ -269,7 +330,7 @@ private void rebalanceByTopic(final String topic, final boolean isOrder) { } if (mqSet != null && cidAll != null) { - List mqAll = new ArrayList(); + List mqAll = new ArrayList<>(); mqAll.addAll(mqSet); Collections.sort(mqAll); @@ -285,12 +346,11 @@ private void rebalanceByTopic(final String topic, final boolean isOrder) { mqAll, cidAll); } catch (Throwable e) { - log.error("AllocateMessageQueueStrategy.allocate Exception. allocateMessageQueueStrategyName={}", strategy.getName(), - e); - return; + log.error("allocate message queue exception. strategy name: {}, ex: {}", strategy.getName(), e); + return false; } - Set allocateResultSet = new HashSet(); + Set allocateResultSet = new HashSet<>(); if (allocateResult != null) { allocateResultSet.addAll(allocateResult); } @@ -298,17 +358,76 @@ private void rebalanceByTopic(final String topic, final boolean isOrder) { boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder); if (changed) { log.info( - "rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, mqAllSize={}, cidAllSize={}, rebalanceResultSize={}, rebalanceResultSet={}", + "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() { @@ -324,12 +443,39 @@ private void truncateMessageQueueNotMyTopic() { } } } + + 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); + } + } + } + + Iterator> clientIter = topicClientRebalance.entrySet().iterator(); + while (clientIter.hasNext()) { + if (!subTable.containsKey(clientIter.next().getKey())) { + clientIter.remove(); + } + } + + Iterator> brokerIter = topicBrokerRebalance.entrySet().iterator(); + while (brokerIter.hasNext()) { + if (!subTable.containsKey(brokerIter.next().getKey())) { + brokerIter.remove(); + } + } } private boolean updateProcessQueueTableInRebalance(final String topic, final Set mqSet, final boolean isOrder) { 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(); @@ -339,50 +485,43 @@ private boolean updateProcessQueueTableInRebalance(final String topic, final Set if (mq.getTopic().equals(topic)) { if (!mqSet.contains(mq)) { pq.setDropped(true); - if (this.removeUnnecessaryMessageQueue(mq, pq)) { - it.remove(); - changed = true; - log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq); - } - } else if (pq.isPullExpired()) { - switch (this.consumeType()) { - case CONSUME_ACTIVELY: - break; - case CONSUME_PASSIVELY: - pq.setDropped(true); - if (this.removeUnnecessaryMessageQueue(mq, pq)) { - it.remove(); - changed = true; - log.error("[BUG]doRebalance, {}, remove unnecessary mq, {}, because pull is pause, so try to fixed it", - consumerGroup, mq); - } - break; - default: - break; - } + 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); } } } - List pullRequestList = new ArrayList(); + // 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 (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 = new ProcessQueue(); - - long nextOffset = -1L; - try { - nextOffset = this.computePullFromWhereWithException(mq); - } catch (Exception e) { - log.info("doRebalance, {}, compute offset failed, {}", consumerGroup, mq); - continue; - } - + ProcessQueue pq = createProcessQueue(topic); + pq.setLocked(true); + long nextOffset = this.computePullFromWhere(mq); if (nextOffset >= 0) { ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq); if (pre != null) { @@ -401,9 +540,201 @@ private boolean updateProcessQueueTableInRebalance(final String topic, final Set log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq); } } + } - this.dispatchPullRequest(pullRequestList); + 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; } @@ -413,6 +744,10 @@ public abstract void messageQueueChanged(final String topic, final Set pullRequestList); + 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 abstract ProcessQueue createProcessQueue(String topicName); public void removeProcessQueue(final MessageQueue mq) { ProcessQueue prev = this.processQueueTable.remove(mq); @@ -444,6 +789,10 @@ public ConcurrentMap getProcessQueueTable() { return processQueueTable; } + public ConcurrentMap getPopProcessQueueTable() { + return popProcessQueueTable; + } + public ConcurrentMap> getTopicSubscribeInfoTable() { return topicSubscribeInfoTable; } @@ -488,5 +837,13 @@ public void destroy() { } this.processQueueTable.clear(); + + Iterator> popIt = this.popProcessQueueTable.entrySet().iterator(); + while (popIt.hasNext()) { + Entry next = popIt.next(); + next.getValue().setDropped(true); + } + this.popProcessQueueTable.clear(); } + } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java index 8fe9400b0f7..335d89b7877 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java @@ -27,8 +27,8 @@ import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; +import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; +import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; public class RebalanceLitePullImpl extends RebalanceImpl { @@ -154,7 +154,30 @@ public long computePullFromWhereWithException(MessageQueue mq) throws MQClientEx } @Override - public void dispatchPullRequest(List pullRequestList) { + 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; + } + + public ProcessQueue createProcessQueue(String topicName) { + return createProcessQueue(); + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java index 6df7eb7f00c..1b5f9766174 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java @@ -23,8 +23,8 @@ 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.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; +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; @@ -81,6 +81,30 @@ public long computePullFromWhereWithException(MessageQueue mq) throws MQClientEx } @Override - public void dispatchPullRequest(List pullRequestList) { + 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; + } + + public ProcessQueue createProcessQueue(String topicName) { + return createProcessQueue(); + } + } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java index 666b696ffa8..df509f37161 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java @@ -26,16 +26,19 @@ 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.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; +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); } @@ -78,7 +81,7 @@ public void messageQueueChanged(String topic, Set mqAll, Set= 0) { result = lastOffset; } else if (-1 == lastOffset) { + //the offset will be fixed by the OFFSET_ILLEGAL process result = 0L; } else { - result = -1; + throw new MQClientException(ResponseCode.QUERY_NOT_FOUND, "Failed to query offset from offset " + + "store"); } break; } @@ -214,7 +230,8 @@ else if (-1 == lastOffset) { } } } else { - result = -1; + throw new MQClientException(ResponseCode.QUERY_NOT_FOUND, "Failed to query offset from offset " + + "store"); } break; } @@ -223,14 +240,57 @@ else if (-1 == lastOffset) { break; } + if (result < 0) { + throw new MQClientException(ResponseCode.SYSTEM_ERROR, "Found unexpected result " + result); + } + return result; } @Override - public void dispatchPullRequest(List pullRequestList) { + 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) { - this.defaultMQPushConsumerImpl.executePullRequestImmediately(pullRequest); - log.info("doRebalance, {}, add a new pull request {}", consumerGroup, pullRequest); + 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 ProcessQueue createProcessQueue(String topicName) { + return createProcessQueue(); + } + + @Override + public PopProcessQueue createPopProcessQueue() { + return new PopProcessQueue(); + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceService.java index c8f8ab14079..56f589d5197 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceService.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceService.java @@ -17,15 +17,15 @@ package org.apache.rocketmq.client.impl.consumer; import org.apache.rocketmq.client.impl.factory.MQClientInstance; -import org.apache.rocketmq.client.log.ClientLogger; import org.apache.rocketmq.common.ServiceThread; -import org.apache.rocketmq.logging.InternalLogger; +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 final InternalLogger log = ClientLogger.getLog(); + private final Logger log = LoggerFactory.getLogger(RebalanceService.class); private final MQClientInstance mqClientFactory; public RebalanceService(MQClientInstance mqClientFactory) { diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java index 0cc5e3e84c2..8851bc81599 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java @@ -16,7 +16,6 @@ */ package org.apache.rocketmq.client.impl.factory; -import java.io.UnsupportedEncodingException; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -30,12 +29,11 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; 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 com.alibaba.fastjson.JSON; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.admin.MQAdminExtInner; @@ -55,70 +53,85 @@ 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.log.ClientLogger; 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.UtilAll; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.filter.ExpressionType; -import org.apache.rocketmq.common.protocol.NamespaceUtil; -import org.apache.rocketmq.common.topic.TopicValidator; -import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult; -import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData; -import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData; -import org.apache.rocketmq.common.protocol.heartbeat.ProducerData; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; +import org.apache.rocketmq.common.message.MessageQueueAssignment; +import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.remoting.RPCHook; -import org.apache.rocketmq.remoting.common.RemotingHelper; +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 org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; + +import static org.apache.rocketmq.remoting.rpc.ClientMetadata.topicRouteData2EndpointsForStaticTopic; public class MQClientInstance { private final static long LOCK_TIMEOUT_MILLIS = 3000; - private final InternalLogger log = ClientLogger.getLog(); + private final static Logger log = LoggerFactory.getLogger(MQClientInstance.class); private final ClientConfig clientConfig; - private final int instanceIndex; private final String clientId; private final long bootTimestamp = System.currentTimeMillis(); - private final ConcurrentMap producerTable = new ConcurrentHashMap(); - private final ConcurrentMap consumerTable = new ConcurrentHashMap(); - private final ConcurrentMap adminExtTable = new ConcurrentHashMap(); + + /** + * 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 topicRouteTable = new ConcurrentHashMap<>(); + private final ConcurrentMap> topicEndPointsTable = new ConcurrentHashMap<>(); private final Lock lockNamesrv = new ReentrantLock(); private final Lock lockHeartbeat = new ReentrantLock(); - private final ConcurrentMap> brokerAddrTable = - new ConcurrentHashMap>(); - private final ConcurrentMap> brokerVersionTable = - new ConcurrentHashMap>(); - private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return new Thread(r, "MQClientFactoryScheduledThread"); - } - }); - private final ClientRemotingProcessor clientRemotingProcessor; + + /** + * The container which stores the brokerClusterInfo. The key of the map is the brokerCluster 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 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 Random random = new Random(); + private final Random random = new Random(); public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String clientId) { this(clientConfig, instanceIndex, clientId, null); @@ -126,12 +139,12 @@ public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String cli public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String clientId, RPCHook rpcHook) { this.clientConfig = clientConfig; - this.instanceIndex = instanceIndex; this.nettyClientConfig = new NettyClientConfig(); this.nettyClientConfig.setClientCallbackExecutorThreads(clientConfig.getClientCallbackExecutorThreads()); this.nettyClientConfig.setUseTLS(clientConfig.isUseTLS()); - this.clientRemotingProcessor = new ClientRemotingProcessor(this); - this.mQClientAPIImpl = new MQClientAPIImpl(this.nettyClientConfig, this.clientRemotingProcessor, rpcHook, clientConfig); + this.nettyClientConfig.setSocksProxyConfig(clientConfig.getSocksProxyConfig()); + ClientRemotingProcessor clientRemotingProcessor = new ClientRemotingProcessor(this); + this.mQClientAPIImpl = new MQClientAPIImpl(this.nettyClientConfig, clientRemotingProcessor, rpcHook, clientConfig); if (this.clientConfig.getNamesrvAddr() != null) { this.mQClientAPIImpl.updateNameServerAddressList(this.clientConfig.getNamesrvAddr()); @@ -152,7 +165,7 @@ public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String cli this.consumerStatsManager = new ConsumerStatsManager(this.scheduledExecutorService); log.info("Created a new client Instance, InstanceIndex:{}, ClientID:{}, ClientConfig:{}, ClientVersion:{}, SerializerType:{}", - this.instanceIndex, + instanceIndex, this.clientId, this.clientConfig, MQVersion.getVersionDesc(MQVersion.CURRENT_VERSION), RemotingCommand.getSerializeTypeConfigInThisServer()); @@ -160,6 +173,7 @@ public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String cli 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(";"); @@ -173,6 +187,13 @@ public static TopicPublishInfo topicRouteData2TopicPublishInfo(final String topi } 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); @@ -208,7 +229,12 @@ public static TopicPublishInfo topicRouteData2TopicPublishInfo(final String topi } public static Set topicRouteData2TopicSubscribeInfo(final String topic, final TopicRouteData route) { - Set mqList = new HashSet(); + 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())) { @@ -255,65 +281,45 @@ public void start() throws MQClientException { private void startScheduledTask() { if (null == this.clientConfig.getNamesrvAddr()) { - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - - @Override - public void run() { - try { - MQClientInstance.this.mQClientAPIImpl.fetchNameServerAddr(); - } catch (Exception e) { - log.error("ScheduledTask fetchNameServerAddr exception", e); - } + this.scheduledExecutorService.scheduleAtFixedRate(() -> { + try { + MQClientInstance.this.mQClientAPIImpl.fetchNameServerAddr(); + } catch (Exception e) { + log.error("ScheduledTask fetchNameServerAddr exception", e); } }, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS); } - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - - @Override - public void run() { - try { - MQClientInstance.this.updateTopicRouteInfoFromNameServer(); - } catch (Exception e) { - log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e); - } + this.scheduledExecutorService.scheduleAtFixedRate(() -> { + try { + MQClientInstance.this.updateTopicRouteInfoFromNameServer(); + } catch (Exception e) { + log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e); } }, 10, this.clientConfig.getPollNameServerInterval(), TimeUnit.MILLISECONDS); - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - - @Override - public void run() { - try { - MQClientInstance.this.cleanOfflineBroker(); - MQClientInstance.this.sendHeartbeatToAllBrokerWithLock(); - } catch (Exception e) { - log.error("ScheduledTask sendHeartbeatToAllBroker exception", e); - } + this.scheduledExecutorService.scheduleAtFixedRate(() -> { + try { + MQClientInstance.this.cleanOfflineBroker(); + MQClientInstance.this.sendHeartbeatToAllBrokerWithLock(); + } catch (Exception e) { + log.error("ScheduledTask sendHeartbeatToAllBroker exception", e); } }, 1000, this.clientConfig.getHeartbeatBrokerInterval(), TimeUnit.MILLISECONDS); - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - - @Override - public void run() { - try { - MQClientInstance.this.persistAllConsumerOffset(); - } catch (Exception e) { - log.error("ScheduledTask persistAllConsumerOffset exception", e); - } + this.scheduledExecutorService.scheduleAtFixedRate(() -> { + try { + MQClientInstance.this.persistAllConsumerOffset(); + } catch (Exception e) { + log.error("ScheduledTask persistAllConsumerOffset exception", e); } }, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS); - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { - - @Override - public void run() { - try { - MQClientInstance.this.adjustThreadPool(); - } catch (Exception e) { - log.error("ScheduledTask adjustThreadPool exception", e); - } + this.scheduledExecutorService.scheduleAtFixedRate(() -> { + try { + MQClientInstance.this.adjustThreadPool(); + } catch (Exception e) { + log.error("ScheduledTask adjustThreadPool exception", e); } }, 1, 1, TimeUnit.MINUTES); } @@ -323,13 +329,11 @@ public String getClientId() { } public void updateTopicRouteInfoFromNameServer() { - Set topicList = new HashSet(); + Set topicList = new HashSet<>(); // Consumer { - Iterator> it = this.consumerTable.entrySet().iterator(); - while (it.hasNext()) { - Entry entry = it.next(); + for (Entry entry : this.consumerTable.entrySet()) { MQConsumerInner impl = entry.getValue(); if (impl != null) { Set subList = impl.subscriptions(); @@ -344,9 +348,7 @@ public void updateTopicRouteInfoFromNameServer() { // Producer { - Iterator> it = this.producerTable.entrySet().iterator(); - while (it.hasNext()) { - Entry entry = it.next(); + for (Entry entry : this.producerTable.entrySet()) { MQProducerInner impl = entry.getValue(); if (impl != null) { Set lst = impl.getPublishTopicList(); @@ -360,13 +362,8 @@ public void updateTopicRouteInfoFromNameServer() { } } - /** - * @param offsetTable - * @param namespace - * @return newOffsetTable - */ public Map parseOffsetTableFromBroker(Map offsetTable, String namespace) { - HashMap newOffsetTable = new HashMap(); + HashMap newOffsetTable = new HashMap<>(offsetTable.size(), 1); if (StringUtils.isNotEmpty(namespace)) { for (Entry entry : offsetTable.entrySet()) { MessageQueue queue = entry.getKey(); @@ -387,7 +384,7 @@ private void cleanOfflineBroker() { try { if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) try { - ConcurrentHashMap> updatedTable = new ConcurrentHashMap>(); + ConcurrentHashMap> updatedTable = new ConcurrentHashMap<>(this.brokerAddrTable.size(), 1); Iterator>> itBrokerTable = this.brokerAddrTable.entrySet().iterator(); while (itBrokerTable.hasNext()) { @@ -395,7 +392,7 @@ private void cleanOfflineBroker() { String brokerName = entry.getKey(); HashMap oneTable = entry.getValue(); - HashMap cloneAddrTable = new HashMap(); + HashMap cloneAddrTable = new HashMap<>(oneTable.size(), 1); cloneAddrTable.putAll(oneTable); Iterator> it = cloneAddrTable.entrySet().iterator(); @@ -428,10 +425,8 @@ private void cleanOfflineBroker() { } public void checkClientInBroker() throws MQClientException { - Iterator> it = this.consumerTable.entrySet().iterator(); - while (it.hasNext()) { - Entry entry = it.next(); + for (Entry entry : this.consumerTable.entrySet()) { Set subscriptionInner = entry.getValue().subscriptions(); if (subscriptionInner == null || subscriptionInner.isEmpty()) { return; @@ -442,7 +437,7 @@ public void checkClientInBroker() throws MQClientException { continue; } // may need to check one broker every cluster... - // assume that the configs of every broker in cluster are the the same. + // assume that the configs of every broker in cluster are the same. String addr = findBrokerAddrByTopic(subscriptionData.getTopic()); if (addr != null) { @@ -465,11 +460,32 @@ public void checkClientInBroker() throws MQClientException { } } + public void sendHeartbeatToAllBrokerWithLockV2(boolean isRebalance) { + if (this.lockHeartbeat.tryLock()) { + try { + if (clientConfig.isUseHeartbeatV2()) { + this.sendHeartbeatToAllBrokerV2(isRebalance); + } else { + this.sendHeartbeatToAllBroker(); + } + } catch (final Exception e) { + log.error("sendHeartbeatToAllBrokerWithLockV2 exception", e); + } finally { + this.lockHeartbeat.unlock(); + } + } else { + log.warn("sendHeartbeatToAllBrokerWithLockV2 lock heartBeat, but failed."); + } + } + public void sendHeartbeatToAllBrokerWithLock() { if (this.lockHeartbeat.tryLock()) { try { - this.sendHeartbeatToAllBroker(); - this.uploadFilterClassSource(); + if (clientConfig.isUseHeartbeatV2()) { + this.sendHeartbeatToAllBrokerV2(false); + } else { + this.sendHeartbeatToAllBroker(); + } } catch (final Exception e) { log.error("sendHeartbeatToAllBroker exception", e); } finally { @@ -481,18 +497,14 @@ public void sendHeartbeatToAllBrokerWithLock() { } private void persistAllConsumerOffset() { - Iterator> it = this.consumerTable.entrySet().iterator(); - while (it.hasNext()) { - Entry entry = it.next(); + for (Entry entry : this.consumerTable.entrySet()) { MQConsumerInner impl = entry.getValue(); impl.persistConsumerOffset(); } } public void adjustThreadPool() { - Iterator> it = this.consumerTable.entrySet().iterator(); - while (it.hasNext()) { - Entry entry = it.next(); + for (Entry entry : this.consumerTable.entrySet()) { MQConsumerInner impl = entry.getValue(); if (impl != null) { try { @@ -500,7 +512,7 @@ public void adjustThreadPool() { DefaultMQPushConsumerImpl dmq = (DefaultMQPushConsumerImpl) impl; dmq.adjustThreadPool(); } - } catch (Exception e) { + } catch (Exception ignored) { } } } @@ -511,9 +523,7 @@ public boolean updateTopicRouteInfoFromNameServer(final String topic) { } private boolean isBrokerAddrExistInTopicRouteTable(final String addr) { - Iterator> it = this.topicRouteTable.entrySet().iterator(); - while (it.hasNext()) { - Entry entry = it.next(); + for (Entry entry : this.topicRouteTable.entrySet()) { TopicRouteData topicRouteData = entry.getValue(); List bds = topicRouteData.getBrokerDatas(); for (BrokerData bd : bds) { @@ -529,7 +539,7 @@ private boolean isBrokerAddrExistInTopicRouteTable(final String addr) { } private void sendHeartbeatToAllBroker() { - final HeartbeatData heartbeatData = this.prepareHeartbeatData(); + final HeartbeatData heartbeatData = this.prepareHeartbeatData(false); final boolean producerEmpty = heartbeatData.getProducerDataSet().isEmpty(); final boolean consumerEmpty = heartbeatData.getConsumerDataSet().isEmpty(); if (producerEmpty && consumerEmpty) { @@ -537,66 +547,119 @@ private void sendHeartbeatToAllBroker() { return; } - if (!this.brokerAddrTable.isEmpty()) { - long times = this.sendHeartbeatTimesTotal.getAndIncrement(); - Iterator>> it = this.brokerAddrTable.entrySet().iterator(); - while (it.hasNext()) { - Entry> entry = it.next(); - 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) { - if (consumerEmpty) { - if (id != MixAll.MASTER_ID) - continue; - } + if (this.brokerAddrTable.isEmpty()) { + return; + } + long times = this.sendHeartbeatTimesTotal.getAndIncrement(); + 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; + } - try { - int version = this.mQClientAPIImpl.sendHearbeat(addr, heartbeatData, clientConfig.getMqClientApiTimeout()); - if (!this.brokerVersionTable.containsKey(brokerName)) { - this.brokerVersionTable.put(brokerName, new HashMap(4)); - } - this.brokerVersionTable.get(brokerName).put(addr, version); - if (times % 20 == 0) { - log.info("send heart beat to broker[{} {} {}] success", brokerName, id, addr); - log.info(heartbeatData.toString()); - } - } catch (Exception e) { - if (this.isBrokerInNameServer(addr)) { - log.info("send heart beat to broker[{} {} {}] failed", brokerName, id, addr, e); - } else { - log.info("send heart beat to broker[{} {} {}] exception, because the broker not up, forget it", brokerName, - id, addr, e); - } - } - } + try { + int version = this.mQClientAPIImpl.sendHeartbeat(addr, heartbeatData, clientConfig.getMqClientApiTimeout()); + if (!this.brokerVersionTable.containsKey(brokerName)) { + this.brokerVersionTable.put(brokerName, new HashMap<>(4)); + } + this.brokerVersionTable.get(brokerName).put(addr, version); + if (times % 20 == 0) { + log.info("send heart beat to broker[{} {} {}] success", brokerName, id, addr); + log.info(heartbeatData.toString()); + } + } 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); } } } } } - private void uploadFilterClassSource() { - Iterator> it = this.consumerTable.entrySet().iterator(); - while (it.hasNext()) { - Entry next = it.next(); - MQConsumerInner consumer = next.getValue(); - if (ConsumeType.CONSUME_PASSIVELY == consumer.consumeType()) { - Set subscriptions = consumer.subscriptions(); - for (SubscriptionData sub : subscriptions) { - if (sub.isClassFilterMode() && sub.getFilterClassSource() != null) { - final String consumerGroup = consumer.groupName(); - final String className = sub.getSubString(); - final String topic = sub.getTopic(); - final String filterClassSource = sub.getFilterClassSource(); - try { - this.uploadFilterClassToAllFilterServer(consumerGroup, className, topic, filterClassSource); - } catch (Exception e) { - log.error("uploadFilterClassToAllFilterServer Exception", e); + private void 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; + } + if (this.brokerAddrTable.isEmpty()) { + return; + } + if (isRebalance) { + resetBrokerAddrHeartbeatFingerprintMap(); + } + long times = this.sendHeartbeatTimesTotal.getAndIncrement(); + 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; + } + 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 HashMap<>(4)); + } + this.brokerVersionTable.get(brokerName).put(addr, version); + if (times % 20 == 0) { + log.info("send heart beat to broker[{} {} {}] success", brokerName, id, addr); + log.info(heartbeatDataWithSub.toString()); + } + } 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); } } } @@ -610,8 +673,7 @@ public boolean updateTopicRouteInfoFromNameServer(final String topic, boolean is try { TopicRouteData topicRouteData; if (isDefault && defaultMQProducer != null) { - topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(), - clientConfig.getMqClientApiTimeout()); + topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(clientConfig.getMqClientApiTimeout()); if (topicRouteData != null) { for (QueueData data : topicRouteData.getQueueDatas()) { int queueNums = Math.min(defaultMQProducer.getDefaultTopicQueueNums(), data.getReadQueueNums()); @@ -624,7 +686,7 @@ public boolean updateTopicRouteInfoFromNameServer(final String topic, boolean is } if (topicRouteData != null) { TopicRouteData old = this.topicRouteTable.get(topic); - boolean changed = topicRouteDataIsChange(old, topicRouteData); + boolean changed = topicRouteData.topicRouteDataChanged(old); if (!changed) { changed = this.isNeedUpdateTopicRouteInfo(topic); } else { @@ -632,19 +694,24 @@ public boolean updateTopicRouteInfoFromNameServer(final String topic, boolean is } if (changed) { - TopicRouteData cloneTopicRouteData = topicRouteData.cloneTopicRouteData(); 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 - if (!producerTable.isEmpty()) { + { TopicPublishInfo publishInfo = topicRouteData2TopicPublishInfo(topic, topicRouteData); publishInfo.setHaveTopicRouterInfo(true); - Iterator> it = this.producerTable.entrySet().iterator(); - while (it.hasNext()) { - Entry entry = it.next(); + for (Entry entry : this.producerTable.entrySet()) { MQProducerInner impl = entry.getValue(); if (impl != null) { impl.updateTopicPublishInfo(topic, publishInfo); @@ -655,15 +722,14 @@ public boolean updateTopicRouteInfoFromNameServer(final String topic, boolean is // Update sub info if (!consumerTable.isEmpty()) { Set subscribeInfo = topicRouteData2TopicSubscribeInfo(topic, topicRouteData); - Iterator> it = this.consumerTable.entrySet().iterator(); - while (it.hasNext()) { - Entry entry = it.next(); + 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; @@ -691,7 +757,7 @@ public boolean updateTopicRouteInfoFromNameServer(final String topic, boolean is return false; } - private HeartbeatData prepareHeartbeatData() { + private HeartbeatData prepareHeartbeatData(boolean isWithoutSub) { HeartbeatData heartbeatData = new HeartbeatData(); // clientID @@ -708,7 +774,9 @@ private HeartbeatData prepareHeartbeatData() { consumerData.setConsumeFromWhere(impl.consumeFromWhere()); consumerData.getSubscriptionDataSet().addAll(impl.subscriptions()); consumerData.setUnitMode(impl.isUnitMode()); - + if (!isWithoutSub) { + consumerData.getSubscriptionDataSet().addAll(impl.subscriptions()); + } heartbeatData.getConsumerDataSet().add(consumerData); } } @@ -723,14 +791,12 @@ private HeartbeatData prepareHeartbeatData() { heartbeatData.getProducerDataSet().add(producerData); } } - + heartbeatData.setWithoutSub(isWithoutSub); return heartbeatData; } private boolean isBrokerInNameServer(final String brokerAddr) { - Iterator> it = this.topicRouteTable.entrySet().iterator(); - while (it.hasNext()) { - Entry itNext = it.next(); + for (Entry itNext : this.topicRouteTable.entrySet()) { List brokerDatas = itNext.getValue().getBrokerDatas(); for (BrokerData bd : brokerDatas) { boolean contain = bd.getBrokerAddrs().containsValue(brokerAddr); @@ -742,85 +808,27 @@ private boolean isBrokerInNameServer(final String brokerAddr) { return false; } - /** - * 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. - */ - @Deprecated - private void uploadFilterClassToAllFilterServer(final String consumerGroup, final String fullClassName, - final String topic, - final String filterClassSource) throws UnsupportedEncodingException { - byte[] classBody = null; - int classCRC = 0; - try { - classBody = filterClassSource.getBytes(MixAll.DEFAULT_CHARSET); - classCRC = UtilAll.crc32(classBody); - } catch (Exception e1) { - log.warn("uploadFilterClassToAllFilterServer Exception, ClassName: {} {}", - fullClassName, - RemotingHelper.exceptionSimpleDesc(e1)); - } - - TopicRouteData topicRouteData = this.topicRouteTable.get(topic); - if (topicRouteData != null - && topicRouteData.getFilterServerTable() != null && !topicRouteData.getFilterServerTable().isEmpty()) { - Iterator>> it = topicRouteData.getFilterServerTable().entrySet().iterator(); - while (it.hasNext()) { - Entry> next = it.next(); - List value = next.getValue(); - for (final String fsAddr : value) { - try { - this.mQClientAPIImpl.registerMessageFilterClass(fsAddr, consumerGroup, topic, fullClassName, classCRC, classBody, - 5000); - - log.info("register message class filter to {} OK, ConsumerGroup: {} Topic: {} ClassName: {}", fsAddr, consumerGroup, - topic, fullClassName); - - } catch (Exception e) { - log.error("uploadFilterClassToAllFilterServer Exception", e); - } - } + 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); } - } else { - log.warn("register message class filter failed, because no filter server, ConsumerGroup: {} Topic: {} ClassName: {}", - consumerGroup, topic, fullClassName); } - } - private boolean topicRouteDataIsChange(TopicRouteData olddata, TopicRouteData nowdata) { - if (olddata == null || nowdata == null) + if (result) { return true; - TopicRouteData old = olddata.cloneTopicRouteData(); - TopicRouteData now = nowdata.cloneTopicRouteData(); - Collections.sort(old.getQueueDatas()); - Collections.sort(old.getBrokerDatas()); - Collections.sort(now.getQueueDatas()); - Collections.sort(now.getBrokerDatas()); - return !old.equals(now); - - } - - private boolean isNeedUpdateTopicRouteInfo(final String topic) { - boolean result = false; - { - Iterator> it = this.producerTable.entrySet().iterator(); - while (it.hasNext() && !result) { - Entry entry = it.next(); - MQProducerInner impl = entry.getValue(); - if (impl != null) { - result = impl.isPublishTopicNeedUpdate(topic); - } - } } - { - Iterator> it = this.consumerTable.entrySet().iterator(); - while (it.hasNext() && !result) { - Entry entry = it.next(); - MQConsumerInner impl = entry.getValue(); - if (impl != null) { - result = impl.isSubscribeTopicNeedUpdate(topic); - } + 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); } } @@ -842,8 +850,6 @@ public void shutdown() { synchronized (this) { switch (this.serviceState) { - case CREATE_JUST: - break; case RUNNING: this.defaultMQProducer.getDefaultMQProducerImpl().shutdown(false); @@ -856,8 +862,8 @@ public void shutdown() { MQClientManager.getInstance().removeClientFactory(this.clientId); log.info("the client factory [{}] shutdown OK", this.clientId); break; + case CREATE_JUST: case SHUTDOWN_ALREADY: - break; default: break; } @@ -884,26 +890,25 @@ public synchronized void unregisterConsumer(final String group) { } private void unregisterClient(final String producerGroup, final String consumerGroup) { - Iterator>> it = this.brokerAddrTable.entrySet().iterator(); - while (it.hasNext()) { - Entry> entry = it.next(); - String brokerName = entry.getKey(); - HashMap oneTable = entry.getValue(); - - if (oneTable != null) { - for (Map.Entry entry1 : oneTable.entrySet()) { - String addr = entry1.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, entry1.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()); - } + 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()); } } } @@ -947,6 +952,14 @@ 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(); } @@ -972,7 +985,46 @@ 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); @@ -986,6 +1038,9 @@ public FindBrokerResult findBrokerAddressInSubscribe( final long brokerId, final boolean onlyThisBroker ) { + if (brokerName == null) { + return null; + } String brokerAddr = null; boolean slave = false; boolean found = false; @@ -1016,7 +1071,7 @@ public FindBrokerResult findBrokerAddressInSubscribe( return null; } - public int findBrokerVersion(String brokerName, String brokerAddr) { + 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); @@ -1044,6 +1099,23 @@ public List findConsumerIdList(final String topic, final String group) { 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) { @@ -1062,7 +1134,7 @@ public synchronized void resetOffset(String topic, String group, Map iterator = processQueueTable.keySet().iterator(); @@ -1106,12 +1178,13 @@ public synchronized void resetOffset(String topic, String group, Map getConsumerStatus(String topic, String group) { MQConsumerInner impl = this.consumerTable.get(group); - if (impl != null && impl instanceof DefaultMQPushConsumerImpl) { + if (impl instanceof DefaultMQPushConsumerImpl) { DefaultMQPushConsumerImpl consumer = (DefaultMQPushConsumerImpl) impl; return consumer.getOffsetStore().cloneOffsetTable(topic); - } else if (impl != null && impl instanceof DefaultMQPullConsumerImpl) { + } else if (impl instanceof DefaultMQPullConsumerImpl) { DefaultMQPullConsumerImpl consumer = (DefaultMQPullConsumerImpl) impl; return consumer.getOffsetStore().cloneOffsetTable(topic); } else { @@ -1155,11 +1228,10 @@ public ConsumeMessageDirectlyResult consumeMessageDirectly(final MessageExt msg, final String consumerGroup, final String brokerName) { MQConsumerInner mqConsumerInner = this.consumerTable.get(consumerGroup); - if (null != mqConsumerInner) { + if (mqConsumerInner instanceof DefaultMQPushConsumerImpl) { DefaultMQPushConsumerImpl consumer = (DefaultMQPushConsumerImpl) mqConsumerInner; - ConsumeMessageDirectlyResult result = consumer.getConsumeMessageService().consumeMessageDirectly(msg, brokerName); - return result; + return consumer.getConsumeMessageService().consumeMessageDirectly(msg, brokerName); } return null; @@ -1191,6 +1263,10 @@ public ConsumerRunningInfo consumerRunningInfo(final String consumerGroup) { return consumerRunningInfo; } + private void resetBrokerAddrHeartbeatFingerprintMap() { + brokerAddrHeartbeatFingerprintTable.clear(); + } + public ConsumerStatsManager getConsumerStatsManager() { return consumerStatsManager; } @@ -1202,4 +1278,13 @@ public NettyClientConfig getNettyClientConfig() { public ClientConfig getClientConfig() { return clientConfig; } + + public TopicRouteData queryTopicRouteData(String topic) { + TopicRouteData data = this.getAnExistTopicRouteData(topic); + if (data == null) { + this.updateTopicRouteInfoFromNameServer(topic); + data = this.getAnExistTopicRouteData(topic); + } + return data; + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/DoNothingClientRemotingProcessor.java b/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/DoNothingClientRemotingProcessor.java new file mode 100644 index 00000000000..9d489f8adaf --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/DoNothingClientRemotingProcessor.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java b/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java new file mode 100644 index 00000000000..fb8f8d11fd4 --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java @@ -0,0 +1,640 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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.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.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.constant.LoggerName; +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.RPCHook; +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.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.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.NotificationRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.NotificationResponseHeader; +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.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.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 + ) { + super(nettyClientConfig, clientRemotingProcessor, rpcHook, clientConfig); + 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; + } + + protected static MQClientException processNullResponseErr(ResponseFuture responseFuture) { + MQClientException ex; + if (!responseFuture.isSendRequestOK()) { + ex = new MQClientException("send request failed", responseFuture.getCause()); + } else if (responseFuture.isTimeout()) { + ex = new MQClientException("wait response timeout " + responseFuture.getTimeoutMillis() + "ms", + responseFuture.getCause()); + } else { + ex = new MQClientException("unknown reason", responseFuture.getCause()); + } + return ex; + } + + public CompletableFuture sendHeartbeatOneway( + String brokerAddr, + HeartbeatData heartbeatData, + long timeoutMillis + ) { + CompletableFuture future = new CompletableFuture<>(); + try { + RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, null); + 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, null); + request.setLanguage(clientConfig.getLanguage()); + request.setBody(heartbeatData.encode()); + + CompletableFuture future = new CompletableFuture<>(); + try { + this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + if (ResponseCode.SUCCESS == response.getCode()) { + future.complete(response.getVersion()); + } else { + future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr)); + } + } else { + future.completeExceptionally(processNullResponseErr(responseFuture)); + } + }); + } catch (Throwable t) { + future.completeExceptionally(t); + } + return future; + } + + 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()); + + CompletableFuture future = new CompletableFuture<>(); + try { + this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + try { + future.complete(this.processSendResponse(brokerName, msg, response, brokerAddr)); + } catch (Exception e) { + future.completeExceptionally(e); + } + } else { + future.completeExceptionally(processNullResponseErr(responseFuture)); + } + }); + } catch (Throwable t) { + future.completeExceptionally(t); + } + return future; + } + + 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); + this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + try { + future.complete(this.processSendResponse(brokerName, msgBatch, response, brokerAddr)); + } catch (Exception e) { + future.completeExceptionally(e); + } + } else { + future.completeExceptionally(processNullResponseErr(responseFuture)); + } + }); + } 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); + + CompletableFuture future = new CompletableFuture<>(); + try { + this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + future.complete(response); + } else { + future.completeExceptionally(processNullResponseErr(responseFuture)); + } + }); + } catch (Throwable t) { + future.completeExceptionally(t); + } + return future; + } + + 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 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 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 + ) { + CompletableFuture future = new CompletableFuture<>(); + try { + RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUMER_OFFSET, requestHeader); + this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + switch (response.getCode()) { + case ResponseCode.SUCCESS: { + try { + QueryConsumerOffsetResponseHeader responseHeader = + (QueryConsumerOffsetResponseHeader) response.decodeCommandCustomHeader(QueryConsumerOffsetResponseHeader.class); + future.complete(responseHeader.getOffset()); + } catch (RemotingCommandException e) { + future.completeExceptionally(e); + } + break; + } + case ResponseCode.QUERY_NOT_FOUND: { + future.completeExceptionally(new OffsetNotFoundException(response.getCode(), response.getRemark(), brokerAddr)); + break; + } + default: + break; + } + } else { + future.completeExceptionally(processNullResponseErr(responseFuture)); + } + }); + } catch (Throwable t) { + future.completeExceptionally(t); + } + return future; + } + + 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> 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, responseFuture -> { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + 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())); + } else { + future.completeExceptionally(processNullResponseErr(responseFuture)); + } + }); + } 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, responseFuture -> { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + 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())); + } else { + future.completeExceptionally(processNullResponseErr(responseFuture)); + } + }); + } 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, responseFuture -> { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + 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())); + } else { + future.completeExceptionally(processNullResponseErr(responseFuture)); + } + }); + } 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); + + CompletableFuture future = new CompletableFuture<>(); + try { + this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + if (response.getCode() == ResponseCode.SUCCESS) { + try { + SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.decodeCommandCustomHeader(SearchOffsetResponseHeader.class); + future.complete(responseHeader.getOffset()); + } catch (Throwable t) { + future.completeExceptionally(t); + } + } + future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark())); + } else { + future.completeExceptionally(processNullResponseErr(responseFuture)); + } + }); + } catch (Throwable t) { + future.completeExceptionally(t); + } + return future; + } + + public CompletableFuture> lockBatchMQWithFuture(String brokerAddr, + LockBatchRequestBody requestBody, long timeoutMillis) { + CompletableFuture> future = new CompletableFuture<>(); + RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.LOCK_BATCH_MQ, null); + request.setBody(requestBody.encode()); + try { + this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + if (response.getCode() == ResponseCode.SUCCESS) { + try { + LockBatchResponseBody responseBody = LockBatchResponseBody.decode(response.getBody(), LockBatchResponseBody.class); + Set messageQueues = responseBody.getLockOKMQSet(); + future.complete(messageQueues); + } catch (Throwable t) { + future.completeExceptionally(t); + } + } + future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark())); + } else { + future.completeExceptionally(processNullResponseErr(responseFuture)); + } + }); + } catch (Exception e) { + future.completeExceptionally(e); + } + return future; + } + + public CompletableFuture unlockBatchMQOneway(String brokerAddr, + UnlockBatchRequestBody requestBody, long timeoutMillis) { + CompletableFuture future = new CompletableFuture<>(); + RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UNLOCK_BATCH_MQ, null); + 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) { + CompletableFuture future = new CompletableFuture<>(); + RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.NOTIFICATION, requestHeader); + try { + this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenAccept(response -> { + if (response.getCode() == ResponseCode.SUCCESS) { + try { + NotificationResponseHeader responseHeader = (NotificationResponseHeader) response.decodeCommandCustomHeader(NotificationResponseHeader.class); + future.complete(responseHeader.isHasMsg()); + } catch (Throwable t) { + future.completeExceptionally(t); + } + } else { + future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark())); + } + }); + } catch (Throwable t) { + future.completeExceptionally(t); + } + return future; + } + + 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; + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIFactory.java b/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIFactory.java new file mode 100644 index 00000000000..f7d9b11ba80 --- /dev/null +++ b/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIFactory.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.utils.StartAndShutdown; +import org.apache.rocketmq.remoting.RPCHook; +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; + + public MQClientAPIFactory(NameserverAccessConfig nameserverAccessConfig, String namePrefix, int clientNum, + ClientRemotingProcessor clientRemotingProcessor, + RPCHook rpcHook, ScheduledExecutorService scheduledExecutorService) { + this.nameserverAccessConfig = nameserverAccessConfig; + this.namePrefix = namePrefix; + this.clientNum = clientNum; + this.clientRemotingProcessor = clientRemotingProcessor; + this.rpcHook = rpcHook; + this.scheduledExecutorService = scheduledExecutorService; + + 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 { + for (int i = 0; i < this.clientNum; i++) { + clients[i].shutdown(); + } + } + + 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); + + if (!mqClientAPIExt.updateNameServerAddressList()) { + this.scheduledExecutorService.scheduleAtFixedRate( + mqClientAPIExt::fetchNameServerAddr, + Duration.ofSeconds(10).toMillis(), + Duration.ofMinutes(2).toMillis(), + TimeUnit.MILLISECONDS + ); + } + mqClientAPIExt.start(); + return mqClientAPIExt; + } +} diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java index f3f9caf43a9..4eb0e69247d 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java @@ -30,10 +30,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ThreadFactory; +import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.client.QueryResult; import org.apache.rocketmq.client.Validators; import org.apache.rocketmq.client.common.ClientErrorCode; @@ -50,7 +49,6 @@ 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.log.ClientLogger; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.LocalTransactionExecuter; import org.apache.rocketmq.client.producer.LocalTransactionState; @@ -67,7 +65,11 @@ 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.compression.CompressionType; +import org.apache.rocketmq.common.compression.Compressor; +import org.apache.rocketmq.common.compression.CompressorFactory; import org.apache.rocketmq.common.help.FAQUrl; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageAccessor; @@ -79,28 +81,29 @@ 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.protocol.NamespaceUtil; -import org.apache.rocketmq.common.protocol.header.CheckTransactionStateRequestHeader; -import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.common.utils.CorrelationIdUtil; -import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.remoting.RPCHook; -import org.apache.rocketmq.remoting.common.RemotingHelper; 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.SendMessageRequestHeader; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class DefaultMQProducerImpl implements MQProducerInner { - private final InternalLogger log = ClientLogger.getLog(); + + 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(); + 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; @@ -108,11 +111,19 @@ public class DefaultMQProducerImpl implements MQProducerInner { protected ExecutorService checkExecutor; private ServiceState serviceState = ServiceState.CREATE_JUST; private MQClientInstance mQClientFactory; - private ArrayList checkForbiddenHookList = new ArrayList(); - private int zipCompressLevel = Integer.parseInt(System.getProperty(MixAll.MESSAGE_COMPRESS_LEVEL, "5")); + private ArrayList checkForbiddenHookList = new ArrayList<>(); private MQFaultStrategy mqFaultStrategy = new MQFaultStrategy(); private ExecutorService asyncSenderExecutor; + // compression related + private int compressLevel = Integer.parseInt(System.getProperty(MixAll.MESSAGE_COMPRESS_LEVEL, "5")); + private CompressionType compressType = CompressionType.of(System.getProperty(MixAll.MESSAGE_COMPRESS_TYPE, "ZLIB")); + private final Compressor compressor = CompressorFactory.getCompressor(compressType); + + // backpressure related + private Semaphore semaphoreAsyncSendNum; + private Semaphore semaphoreAsyncSendSize; + public DefaultMQProducerImpl(final DefaultMQProducer defaultMQProducer) { this(defaultMQProducer, null); } @@ -121,21 +132,27 @@ public DefaultMQProducerImpl(final DefaultMQProducer defaultMQProducer, RPCHook this.defaultMQProducer = defaultMQProducer; this.rpcHook = rpcHook; - this.asyncSenderThreadPoolQueue = new LinkedBlockingQueue(50000); + this.asyncSenderThreadPoolQueue = new LinkedBlockingQueue<>(50000); this.defaultAsyncSenderExecutor = new ThreadPoolExecutor( Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors(), 1000 * 60, TimeUnit.MILLISECONDS, this.asyncSenderThreadPoolQueue, - new ThreadFactory() { - private AtomicInteger threadIndex = new AtomicInteger(0); + 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."); + } - @Override - public Thread newThread(Runnable r) { - return new Thread(r, "AsyncSenderExecutor_" + this.threadIndex.incrementAndGet()); - } - }); + 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."); + } } public void registerCheckForbiddenHook(CheckForbiddenHook checkForbiddenHook) { @@ -144,12 +161,20 @@ public void registerCheckForbiddenHook(CheckForbiddenHook checkForbiddenHook) { checkForbiddenHookList.size()); } + public void setSemaphoreAsyncSendNum(int num) { + semaphoreAsyncSendNum = new Semaphore(num, true); + } + + public void setSemaphoreAsyncSendSize(int size) { + semaphoreAsyncSendSize = new Semaphore(size, true); + } + public void initTransactionEnv() { TransactionMQProducer producer = (TransactionMQProducer) this.defaultMQProducer; if (producer.getExecutorService() != null) { this.checkExecutor = producer.getExecutorService(); } else { - this.checkRequestQueue = new LinkedBlockingQueue(producer.getCheckRequestHoldMax()); + this.checkRequestQueue = new LinkedBlockingQueue<>(producer.getCheckRequestHoldMax()); this.checkExecutor = new ThreadPoolExecutor( producer.getCheckThreadPoolMinSize(), producer.getCheckThreadPoolMaxSize(), @@ -200,8 +225,6 @@ public void start(final boolean startFactory) throws MQClientException { null); } - this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo()); - if (startFactory) { mQClientFactory.start(); } @@ -230,10 +253,6 @@ public void start(final boolean startFactory) throws MQClientException { private void checkConfig() throws MQClientException { Validators.checkGroup(this.defaultMQProducer.getProducerGroup()); - if (null == this.defaultMQProducer.getProducerGroup()) { - throw new MQClientException("producerGroup is null", null); - } - 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); @@ -267,12 +286,7 @@ public void shutdown(final boolean shutdownFactory) { @Override public Set getPublishTopicList() { - Set topicList = new HashSet(); - for (String key : this.topicPublishInfoTable.keySet()) { - topicList.add(key); - } - - return topicList; + return new HashSet<>(this.topicPublishInfoTable.keySet()); } @Override @@ -324,11 +338,9 @@ public void run() { try { if (transactionCheckListener != null) { localTransactionState = transactionCheckListener.checkLocalTransactionState(message); - } else if (transactionListener != null) { - log.debug("Used new check API in transaction message"); - localTransactionState = transactionListener.checkLocalTransaction(message); } else { - log.warn("CheckTransactionState, pick transactionListener by group[{}] failed", group); + 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); @@ -353,6 +365,7 @@ private void processTransactionState( thisHeader.setProducerGroup(producerGroup); thisHeader.setTranStateTableOffset(checkRequestHeader.getTranStateTableOffset()); thisHeader.setFromTransactionCheck(true); + thisHeader.setBname(checkRequestHeader.getBname()); String uniqueKey = message.getProperties().get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); if (uniqueKey == null) { @@ -378,7 +391,7 @@ private void processTransactionState( String remark = null; if (exception != null) { - remark = "checkLocalTransactionState Exception: " + RemotingHelper.exceptionSimpleDesc(exception); + remark = "checkLocalTransactionState Exception: " + UtilAll.exceptionSimpleDesc(exception); } doExecuteEndTransactionHook(msg, uniqueKey, brokerAddr, localTransactionState, true); @@ -399,7 +412,7 @@ public void updateTopicPublishInfo(final String topic, final TopicPublishInfo in if (info != null && topic != null) { TopicPublishInfo prev = this.topicPublishInfoTable.put(topic, info); if (prev != null) { - log.info("updateTopicPublishInfo prev is not null, " + prev.toString()); + log.info("updateTopicPublishInfo prev is not null, " + prev); } } } @@ -418,7 +431,7 @@ public void createTopic(String key, String newTopic, int queueNum, int topicSysF Validators.checkTopic(newTopic); Validators.isSystemTopic(newTopic); - this.mQClientFactory.getMQAdminImpl().createTopic(key, newTopic, queueNum, topicSysFlag); + this.mQClientFactory.getMQAdminImpl().createTopic(key, newTopic, queueNum, topicSysFlag, null); } private void makeSureStateOK() throws MQClientException { @@ -483,40 +496,79 @@ public void send(Message msg, } /** - * @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 - * * @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 { 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, sendCallback, timeout - costTime); + } catch (Exception e) { + sendCallback.onException(e); + } + } else { + sendCallback.onException( + new RemotingTooMuchRequestException("DEFAULT ASYNC send call timeout")); + } + } + }; + executeAsyncMessageSend(runnable, msg, sendCallback, timeout, beginStartTime); + } + + public void executeAsyncMessageSend(Runnable runnable, final Message msg, final SendCallback sendCallback, + final long timeout, final long beginStartTime) + throws MQClientException, InterruptedException { ExecutorService executor = this.getAsyncSenderExecutor(); + boolean isEnableBackpressureForAsyncMode = this.getDefaultMQProducer().isEnableBackpressureForAsyncMode(); + boolean isSemaphoreAsyncNumAquired = false; + boolean isSemaphoreAsyncSizeAquired = false; + int msgLen = msg.getBody() == null ? 1 : msg.getBody().length; + try { - executor.submit(new Runnable() { - @Override - public void run() { - long costTime = System.currentTimeMillis() - beginStartTime; - if (timeout > costTime) { - try { - sendDefaultImpl(msg, CommunicationMode.ASYNC, sendCallback, timeout - costTime); - } catch (Exception e) { - sendCallback.onException(e); - } - } else { - sendCallback.onException( - new RemotingTooMuchRequestException("DEFAULT ASYNC send call timeout")); - } + if (isEnableBackpressureForAsyncMode) { + long costTime = System.currentTimeMillis() - beginStartTime; + isSemaphoreAsyncNumAquired = timeout - costTime > 0 + && semaphoreAsyncSendNum.tryAcquire(timeout - costTime, TimeUnit.MILLISECONDS); + if (!isSemaphoreAsyncNumAquired) { + sendCallback.onException( + new RemotingTooMuchRequestException("send message tryAcquire semaphoreAsyncNum timeout")); + return; } + costTime = System.currentTimeMillis() - beginStartTime; + isSemaphoreAsyncSizeAquired = timeout - costTime > 0 + && semaphoreAsyncSendSize.tryAcquire(msgLen, timeout - costTime, TimeUnit.MILLISECONDS); + if (!isSemaphoreAsyncSizeAquired) { + sendCallback.onException( + new RemotingTooMuchRequestException("send message tryAcquire semaphoreAsyncSize timeout")); + return; + } + } - }); + executor.submit(runnable); } catch (RejectedExecutionException e) { - throw new MQClientException("executor rejected ", e); + if (isEnableBackpressureForAsyncMode) { + runnable.run(); + } else { + throw new MQClientException("executor rejected ", e); + } + } finally { + if (isSemaphoreAsyncSizeAquired) { + semaphoreAsyncSendSize.release(msgLen); + } + if (isSemaphoreAsyncNumAquired) { + semaphoreAsyncSendNum.release(); + } } } @@ -530,7 +582,7 @@ public void updateFaultItem(final String brokerName, final long currentLatency, } private void validateNameServerSetting() throws MQClientException { - List nsList = this.getmQClientFactory().getMQClientAPIImpl().getNameServerAddressList(); + 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); @@ -596,25 +648,22 @@ private SendResult sendDefaultImpl( default: break; } - } catch (RemotingException e) { - endTimestamp = System.currentTimeMillis(); - this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true); - log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e); - log.warn(msg.toString()); - exception = e; - continue; - } catch (MQClientException e) { + } catch (RemotingException | MQClientException e) { endTimestamp = System.currentTimeMillis(); this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true); - log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e); - log.warn(msg.toString()); + log.warn("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", 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); - log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e); - log.warn(msg.toString()); + log.warn("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq, e); + if (log.isDebugEnabled()) { + log.debug(msg.toString()); + } exception = e; if (this.defaultMQProducer.getRetryResponseCodes().contains(e.getResponseCode())) { continue; @@ -628,11 +677,10 @@ private SendResult sendDefaultImpl( } catch (InterruptedException e) { endTimestamp = System.currentTimeMillis(); this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false); - log.warn(String.format("sendKernelImpl exception, throw exception, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e); - log.warn(msg.toString()); - - log.warn("sendKernelImpl exception", e); - log.warn(msg.toString()); + log.warn("sendKernelImpl exception, throw exception, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq, e); + if (log.isDebugEnabled()) { + log.debug(msg.toString()); + } throw e; } } else { @@ -643,7 +691,6 @@ private SendResult sendDefaultImpl( 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, @@ -700,10 +747,12 @@ private SendResult sendKernelImpl(final Message msg, final TopicPublishInfo topicPublishInfo, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { long beginStartTime = System.currentTimeMillis(); - String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName()); + String brokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(mq); + String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(brokerName); if (null == brokerAddr) { tryToFindTopicPublishInfo(mq.getTopic()); - brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName()); + brokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(mq); + brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(brokerName); } SendMessageContext context = null; @@ -727,6 +776,7 @@ private SendResult sendKernelImpl(final Message msg, boolean msgBodyCompressed = false; if (this.tryToCompressMessage(msg)) { sysFlag |= MessageSysFlag.COMPRESSED_FLAG; + sysFlag |= compressType.getCompressionFlag(); msgBodyCompressed = true; } @@ -781,6 +831,7 @@ private SendResult sendKernelImpl(final Message msg, requestHeader.setReconsumeTimes(0); requestHeader.setUnitMode(this.isUnitMode()); requestHeader.setBatch(msg instanceof MessageBatch); + requestHeader.setBname(brokerName); if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { String reconsumeTimes = MessageAccessor.getReconsumeTime(msg); if (reconsumeTimes != null) { @@ -823,7 +874,7 @@ private SendResult sendKernelImpl(final Message msg, } sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage( brokerAddr, - mq.getBrokerName(), + brokerName, tmpMessage, requestHeader, timeout - costTimeAsync, @@ -843,7 +894,7 @@ private SendResult sendKernelImpl(final Message msg, } sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage( brokerAddr, - mq.getBrokerName(), + brokerName, msg, requestHeader, timeout - costTimeSync, @@ -862,19 +913,7 @@ private SendResult sendKernelImpl(final Message msg, } return sendResult; - } catch (RemotingException e) { - if (this.hasSendMessageHook()) { - context.setException(e); - this.executeSendMessageHookAfter(context); - } - throw e; - } catch (MQBrokerException e) { - if (this.hasSendMessageHook()) { - context.setException(e); - this.executeSendMessageHookAfter(context); - } - throw e; - } catch (InterruptedException e) { + } catch (RemotingException | InterruptedException | MQBrokerException e) { if (this.hasSendMessageHook()) { context.setException(e); this.executeSendMessageHookAfter(context); @@ -886,30 +925,37 @@ private SendResult sendKernelImpl(final Message msg, } } - throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null); + 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 dose not support compressing right now + //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 = UtilAll.compress(body, zipCompressLevel); + byte[] data = compressor.compress(body, compressLevel); if (data != null) { msg.setBody(data); return true; } } catch (IOException e) { log.error("tryToCompressMessage exception", e); - log.warn(msg.toString()); + if (log.isDebugEnabled()) { + log.debug(msg.toString()); + } } } } @@ -1034,10 +1080,6 @@ public void send(Message msg, MessageQueue mq, SendCallback sendCallback) } /** - * @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 - * * @param msg * @param mq * @param sendCallback @@ -1045,45 +1087,43 @@ public void send(Message msg, MessageQueue mq, SendCallback sendCallback) * @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 { + final long beginStartTime = System.currentTimeMillis(); - ExecutorService executor = this.getAsyncSenderExecutor(); - try { - executor.submit(new Runnable() { - @Override - public void run() { - try { - makeSureStateOK(); - Validators.checkMessage(msg, defaultMQProducer); + Runnable runnable = new Runnable() { + @Override + public void run() { + try { + makeSureStateOK(); + Validators.checkMessage(msg, 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) { - try { - sendKernelImpl(msg, mq, CommunicationMode.ASYNC, sendCallback, null, - timeout - costTime); - } catch (MQBrokerException e) { - throw new MQClientException("unknown exception", e); - } - } else { - sendCallback.onException(new RemotingTooMuchRequestException("call timeout")); + 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, sendCallback, null, + timeout - costTime); + } catch (MQBrokerException e) { + throw new MQClientException("unknown exception", e); } - } catch (Exception e) { - sendCallback.onException(e); + } else { + sendCallback.onException(new RemotingTooMuchRequestException("call timeout")); } - + } catch (Exception e) { + sendCallback.onException(e); } + } - }); - } catch (RejectedExecutionException e) { - throw new MQClientException("executor rejected ", e); - } + }; + executeAsyncMessageSend(runnable, msg, sendCallback, timeout, beginStartTime); } /** @@ -1180,33 +1220,30 @@ public void send(Message msg, MessageQueueSelector selector, Object arg, SendCal public void send(final Message msg, final MessageQueueSelector selector, final Object arg, final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, InterruptedException { + final long beginStartTime = System.currentTimeMillis(); - ExecutorService executor = this.getAsyncSenderExecutor(); - try { - executor.submit(new Runnable() { - @Override - public void run() { - long costTime = System.currentTimeMillis() - beginStartTime; - if (timeout > costTime) { + Runnable runnable = new Runnable() { + @Override + public void run() { + long costTime = System.currentTimeMillis() - beginStartTime; + if (timeout > costTime) { + try { try { - try { - sendSelectImpl(msg, selector, arg, CommunicationMode.ASYNC, sendCallback, - timeout - costTime); - } catch (MQBrokerException e) { - throw new MQClientException("unknown exception", e); - } - } catch (Exception e) { - sendCallback.onException(e); + sendSelectImpl(msg, selector, arg, CommunicationMode.ASYNC, sendCallback, + timeout - costTime); + } catch (MQBrokerException e) { + throw new MQClientException("unknown exception", e); } - } else { - sendCallback.onException(new RemotingTooMuchRequestException("call timeout")); + } catch (Exception e) { + sendCallback.onException(e); } + } else { + sendCallback.onException(new RemotingTooMuchRequestException("call timeout")); } + } - }); - } catch (RejectedExecutionException e) { - throw new MQClientException("executor rejected ", e); - } + }; + executeAsyncMessageSend(runnable, msg, sendCallback, timeout, beginStartTime); } /** @@ -1259,7 +1296,7 @@ public TransactionSendResult sendMessageInTransaction(final Message msg, } if (null != localTransactionExecuter) { localTransactionState = localTransactionExecuter.executeLocalTransactionBranch(msg, arg); - } else if (transactionListener != null) { + } else { log.debug("Used new transaction API"); localTransactionState = transactionListener.executeLocalTransaction(msg, arg); } @@ -1268,12 +1305,12 @@ public TransactionSendResult sendMessageInTransaction(final Message msg, } if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE) { - log.info("executeLocalTransactionBranch return {}", localTransactionState); - log.info(msg.toString()); + log.info("executeLocalTransactionBranch return: {} messageTopic: {} transactionId: {} tag: {} key: {}", + localTransactionState, msg.getTopic(), msg.getTransactionId(), msg.getTags(), msg.getKeys()); } } catch (Throwable e) { - log.info("executeLocalTransactionBranch exception", e); - log.info(msg.toString()); + log.error("executeLocalTransactionBranch exception, messageTopic: {} transactionId: {} tag: {} key: {}", + msg.getTopic(), msg.getTransactionId(), msg.getTags(), msg.getKeys(), e); localException = e; } } @@ -1323,10 +1360,12 @@ public void endTransaction( id = MessageDecoder.decodeMessageId(sendResult.getMsgId()); } String transactionId = sendResult.getTransactionId(); - final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(sendResult.getMessageQueue().getBrokerName()); + final String destBrokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(defaultMQProducer.queueWithNamespace(sendResult.getMessageQueue())); + final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(destBrokerName); EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader(); requestHeader.setTransactionId(transactionId); requestHeader.setCommitLogOffset(id.getOffset()); + requestHeader.setBname(destBrokerName); switch (localTransactionState) { case COMMIT_MESSAGE: requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_COMMIT_TYPE); @@ -1367,7 +1406,7 @@ public SendResult send(Message msg, return this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout); } - public Message request(Message msg, + public Message request(final Message msg, long timeout) throws RequestTimeoutException, MQClientException, RemotingException, MQBrokerException, InterruptedException { long beginTimestamp = System.currentTimeMillis(); prepareSendRequest(msg, timeout); @@ -1412,6 +1451,7 @@ public void request(Message msg, final RequestCallback requestCallback, long tim @Override public void onSuccess(SendResult sendResult) { requestResponseFuture.setSendRequestOk(true); + requestResponseFuture.executeRequestCallback(); } @Override @@ -1564,16 +1604,16 @@ private void requestFail(final String correlationId) { private void prepareSendRequest(final Message msg, long timeout) { String correlationId = CorrelationIdUtil.createCorrelationId(); - String requestClientId = this.getmQClientFactory().getClientId(); + 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()); + boolean hasRouteData = this.getMqClientFactory().getTopicRouteTable().containsKey(msg.getTopic()); if (!hasRouteData) { long beginTimestamp = System.currentTimeMillis(); this.tryToFindTopicPublishInfo(msg.getTopic()); - this.getmQClientFactory().sendHeartbeatToAllBrokerWithLock(); + this.getMqClientFactory().sendHeartbeatToAllBrokerWithLock(); long cost = System.currentTimeMillis() - beginTimestamp; if (cost > 500) { log.warn("prepare send request for <{}> cost {} ms", msg.getTopic(), cost); @@ -1585,12 +1625,20 @@ public ConcurrentMap getTopicPublishInfoTable() { return topicPublishInfoTable; } - public int getZipCompressLevel() { - return zipCompressLevel; + public int getCompressLevel() { + return compressLevel; + } + + public void setCompressLevel(int compressLevel) { + this.compressLevel = compressLevel; + } + + public CompressionType getCompressType() { + return compressType; } - public void setZipCompressLevel(int zipCompressLevel) { - this.zipCompressLevel = zipCompressLevel; + public void setCompressType(CompressionType compressType) { + this.compressType = compressType; } public ServiceState getServiceState() { diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/producer/MQProducerInner.java b/client/src/main/java/org/apache/rocketmq/client/impl/producer/MQProducerInner.java index acfd7b1f2c1..934a28073df 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/producer/MQProducerInner.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/producer/MQProducerInner.java @@ -20,7 +20,7 @@ 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.common.protocol.header.CheckTransactionStateRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader; public interface MQProducerInner { Set getPublishTopicList(); diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/producer/TopicPublishInfo.java b/client/src/main/java/org/apache/rocketmq/client/impl/producer/TopicPublishInfo.java index 2f8337edefd..a5f8405001b 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/producer/TopicPublishInfo.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/producer/TopicPublishInfo.java @@ -20,13 +20,13 @@ import java.util.List; import org.apache.rocketmq.client.common.ThreadLocalIndex; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; +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 List messageQueueList = new ArrayList<>(); private volatile ThreadLocalIndex sendWhichQueue = new ThreadLocalIndex(); private TopicRouteData topicRouteData; @@ -72,9 +72,7 @@ public MessageQueue selectOneMessageQueue(final String lastBrokerName) { } else { for (int i = 0; i < this.messageQueueList.size(); i++) { int index = this.sendWhichQueue.incrementAndGet(); - int pos = Math.abs(index) % this.messageQueueList.size(); - if (pos < 0) - pos = 0; + int pos = index % this.messageQueueList.size(); MessageQueue mq = this.messageQueueList.get(pos); if (!mq.getBrokerName().equals(lastBrokerName)) { return mq; @@ -86,13 +84,12 @@ public MessageQueue selectOneMessageQueue(final String lastBrokerName) { public MessageQueue selectOneMessageQueue() { int index = this.sendWhichQueue.incrementAndGet(); - int pos = Math.abs(index) % this.messageQueueList.size(); - if (pos < 0) - pos = 0; + int pos = index % this.messageQueueList.size(); + return this.messageQueueList.get(pos); } - public int getQueueIdByBroker(final String brokerName) { + public int getWriteQueueIdByBroker(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)) { diff --git a/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java b/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java index 827d97265f5..93795d95753 100644 --- a/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java @@ -25,9 +25,9 @@ import org.apache.rocketmq.client.common.ThreadLocalIndex; public class LatencyFaultToleranceImpl implements LatencyFaultTolerance { - private final ConcurrentHashMap faultItemTable = new ConcurrentHashMap(16); + private final ConcurrentHashMap faultItemTable = new ConcurrentHashMap<>(16); - private final ThreadLocalIndex whichItemWorst = new ThreadLocalIndex(); + private final ThreadLocalIndex randomItem = new ThreadLocalIndex(); @Override public void updateFaultItem(final String name, final long currentLatency, final long notAvailableDuration) { @@ -65,26 +65,21 @@ public void remove(final String name) { @Override public String pickOneAtLeast() { final Enumeration elements = this.faultItemTable.elements(); - List tmpList = new LinkedList(); + List tmpList = new LinkedList<>(); while (elements.hasMoreElements()) { final FaultItem faultItem = elements.nextElement(); tmpList.add(faultItem); } - if (!tmpList.isEmpty()) { - Collections.shuffle(tmpList); - Collections.sort(tmpList); - final int half = tmpList.size() / 2; if (half <= 0) { return tmpList.get(0).getName(); } else { - final int i = this.whichItemWorst.incrementAndGet() % half; + final int i = this.randomItem.incrementAndGet() % half; return tmpList.get(i).getName(); } } - return null; } @@ -92,7 +87,7 @@ public String pickOneAtLeast() { public String toString() { return "LatencyFaultToleranceImpl{" + "faultItemTable=" + faultItemTable + - ", whichItemWorst=" + whichItemWorst + + ", whichItemWorst=" + randomItem + '}'; } diff --git a/client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java b/client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java index ea3d07e6d0f..e86238e55b9 100644 --- a/client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java +++ b/client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java @@ -17,13 +17,14 @@ package org.apache.rocketmq.client.latency; +import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; -import org.apache.rocketmq.client.log.ClientLogger; -import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.common.message.MessageQueue; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class MQFaultStrategy { - private final static InternalLogger log = ClientLogger.getLog(); + private final static Logger log = LoggerFactory.getLogger(MQFaultStrategy.class); private final LatencyFaultTolerance latencyFaultTolerance = new LatencyFaultToleranceImpl(); private boolean sendLatencyFaultEnable = false; @@ -60,16 +61,15 @@ public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final S try { int index = tpInfo.getSendWhichQueue().incrementAndGet(); for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) { - int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size(); - if (pos < 0) - pos = 0; + int pos = index++ % tpInfo.getMessageQueueList().size(); MessageQueue mq = tpInfo.getMessageQueueList().get(pos); - if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) + if (!StringUtils.equals(lastBrokerName, mq.getBrokerName()) && latencyFaultTolerance.isAvailable(mq.getBrokerName())) { return mq; + } } final String notBestBroker = latencyFaultTolerance.pickOneAtLeast(); - int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker); + int writeQueueNums = tpInfo.getWriteQueueIdByBroker(notBestBroker); if (writeQueueNums > 0) { final MessageQueue mq = tpInfo.selectOneMessageQueue(); if (notBestBroker != null) { diff --git a/client/src/main/java/org/apache/rocketmq/client/log/ClientLogger.java b/client/src/main/java/org/apache/rocketmq/client/log/ClientLogger.java deleted file mode 100644 index b40f6a5159e..00000000000 --- a/client/src/main/java/org/apache/rocketmq/client/log/ClientLogger.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF 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.log; - -import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InnerLoggerFactory; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.logging.inner.Appender; -import org.apache.rocketmq.logging.inner.Layout; -import org.apache.rocketmq.logging.inner.Level; -import org.apache.rocketmq.logging.inner.Logger; -import org.apache.rocketmq.logging.inner.LoggingBuilder; -import org.apache.rocketmq.logging.inner.LoggingEvent; -import org.apache.rocketmq.remoting.common.RemotingHelper; - -public class ClientLogger { - - public static final String CLIENT_LOG_USESLF4J = "rocketmq.client.logUseSlf4j"; - public static final String CLIENT_LOG_ROOT = "rocketmq.client.logRoot"; - public static final String CLIENT_LOG_MAXINDEX = "rocketmq.client.logFileMaxIndex"; - public static final String CLIENT_LOG_FILESIZE = "rocketmq.client.logFileMaxSize"; - public static final String CLIENT_LOG_LEVEL = "rocketmq.client.logLevel"; - public static final String CLIENT_LOG_ADDITIVE = "rocketmq.client.log.additive"; - public static final String CLIENT_LOG_FILENAME = "rocketmq.client.logFileName"; - public static final String CLIENT_LOG_ASYNC_QUEUESIZE = "rocketmq.client.logAsyncQueueSize"; - public static final String ROCKETMQ_CLIENT_APPENDER_NAME = "RocketmqClientAppender"; - - private static final InternalLogger CLIENT_LOGGER; - - private static final boolean CLIENT_USE_SLF4J; - - //private static Appender rocketmqClientAppender = null; - - static { - CLIENT_USE_SLF4J = Boolean.parseBoolean(System.getProperty(CLIENT_LOG_USESLF4J, "false")); - if (!CLIENT_USE_SLF4J) { - InternalLoggerFactory.setCurrentLoggerType(InnerLoggerFactory.LOGGER_INNER); - CLIENT_LOGGER = createLogger(LoggerName.CLIENT_LOGGER_NAME); - createLogger(LoggerName.COMMON_LOGGER_NAME); - createLogger(RemotingHelper.ROCKETMQ_REMOTING); - } else { - CLIENT_LOGGER = InternalLoggerFactory.getLogger(LoggerName.CLIENT_LOGGER_NAME); - } - } - - private static synchronized Appender createClientAppender() { - String clientLogRoot = System.getProperty(CLIENT_LOG_ROOT, System.getProperty("user.home") + "/logs/rocketmqlogs"); - String clientLogMaxIndex = System.getProperty(CLIENT_LOG_MAXINDEX, "10"); - String clientLogFileName = System.getProperty(CLIENT_LOG_FILENAME, "rocketmq_client.log"); - String maxFileSize = System.getProperty(CLIENT_LOG_FILESIZE, "1073741824"); - String asyncQueueSize = System.getProperty(CLIENT_LOG_ASYNC_QUEUESIZE, "1024"); - - String logFileName = clientLogRoot + "/" + clientLogFileName; - - int maxFileIndex = Integer.parseInt(clientLogMaxIndex); - int queueSize = Integer.parseInt(asyncQueueSize); - - Layout layout = LoggingBuilder.newLayoutBuilder().withDefaultLayout().build(); - - Appender rocketmqClientAppender = LoggingBuilder.newAppenderBuilder() - .withRollingFileAppender(logFileName, maxFileSize, maxFileIndex) - .withAsync(false, queueSize).withName(ROCKETMQ_CLIENT_APPENDER_NAME).withLayout(layout).build(); - - Logger.getRootLogger().addAppender(rocketmqClientAppender); - return rocketmqClientAppender; - } - - private static InternalLogger createLogger(final String loggerName) { - String clientLogLevel = System.getProperty(CLIENT_LOG_LEVEL, "INFO"); - boolean additive = "true".equalsIgnoreCase(System.getProperty(CLIENT_LOG_ADDITIVE)); - InternalLogger logger = InternalLoggerFactory.getLogger(loggerName); - InnerLoggerFactory.InnerLogger innerLogger = (InnerLoggerFactory.InnerLogger) logger; - Logger realLogger = innerLogger.getLogger(); - - //if (rocketmqClientAppender == null) { - // createClientAppender(); - //} - - realLogger.addAppender(new AppenderProxy()); - realLogger.setLevel(Level.toLevel(clientLogLevel)); - realLogger.setAdditivity(additive); - return logger; - } - - public static InternalLogger getLog() { - return CLIENT_LOGGER; - } - - static class AppenderProxy extends Appender { - private Appender proxy; - - @Override - protected void append(LoggingEvent event) { - if (null == proxy) { - proxy = ClientLogger.createClientAppender(); - } - proxy.doAppend(event); - } - - @Override - public void close() { - if (null != proxy) { - proxy.close(); - } - } - } -} diff --git a/client/src/main/java/org/apache/rocketmq/client/producer/DefaultMQProducer.java b/client/src/main/java/org/apache/rocketmq/client/producer/DefaultMQProducer.java index 9f91b411e88..6e9ffed8c0c 100644 --- a/client/src/main/java/org/apache/rocketmq/client/producer/DefaultMQProducer.java +++ b/client/src/main/java/org/apache/rocketmq/client/producer/DefaultMQProducer.java @@ -19,6 +19,7 @@ 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; @@ -29,7 +30,6 @@ import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.exception.RequestTimeoutException; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; -import org.apache.rocketmq.client.log.ClientLogger; import org.apache.rocketmq.client.trace.AsyncTraceDispatcher; import org.apache.rocketmq.client.trace.TraceDispatcher; import org.apache.rocketmq.client.trace.hook.EndTransactionTraceHookImpl; @@ -38,15 +38,14 @@ 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.MessageId; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.ResponseCode; import org.apache.rocketmq.common.topic.TopicValidator; -import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; +import org.apache.rocketmq.remoting.protocol.ResponseCode; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * This class is the entry point for applications intending to send messages.

@@ -54,7 +53,7 @@ * 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 brokers. Each of them has pros and + * 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 @@ -66,14 +65,14 @@ 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 InternalLogger log = ClientLogger.getLog(); - private final Set retryResponseCodes = new CopyOnWriteArraySet(Arrays.asList( - ResponseCode.TOPIC_NOT_EXIST, - ResponseCode.SERVICE_NOT_AVAILABLE, - ResponseCode.SYSTEM_ERROR, - ResponseCode.NO_PERMISSION, - ResponseCode.NO_BUYER_ID, - ResponseCode.NOT_IN_CURRENT_UNIT + 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.NO_PERMISSION, + ResponseCode.NO_BUYER_ID, + ResponseCode.NOT_IN_CURRENT_UNIT )); /** @@ -82,7 +81,7 @@ public class DefaultMQProducer extends ClientConfig implements MQProducer { * * For non-transactional messages, it does not matter as long as it's unique per process.

* - * See {@linktourl http://rocketmq.apache.org/docs/core-concept/} for more discussion. + * See core concepts for more discussion. */ private String producerGroup; @@ -126,7 +125,7 @@ public class DefaultMQProducer extends ClientConfig implements MQProducer { private boolean retryAnotherBrokerWhenNotStoreOK = false; /** - * Maximum allowed message size in bytes. + * Maximum allowed message body size in bytes. */ private int maxMessageSize = 1024 * 1024 * 4; // 4M @@ -135,6 +134,23 @@ public class DefaultMQProducer extends ClientConfig implements MQProducer { */ private TraceDispatcher traceDispatcher = 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 10000 + */ + private int backPressureForAsyncSendNum = 10000; + + /** + * on BackpressureForAsyncMode, limit maximum message size of on-going sending async messages + * default is 100M + */ + private int backPressureForAsyncSendSize = 100 * 1024 * 1024; + /** * Default constructor. */ @@ -256,7 +272,7 @@ public DefaultMQProducer(final String namespace, final String producerGroup, RPC this.defaultMQProducerImpl.registerEndTransactionHook( new EndTransactionTraceHookImpl(traceDispatcher)); } catch (Throwable e) { - log.error("system mqtrace hook init failed ,maybe can't send msg trace data"); + logger.error("system mqtrace hook init failed ,maybe can't send msg trace data"); } } } @@ -264,11 +280,11 @@ public DefaultMQProducer(final String namespace, final String producerGroup, RPC @Override public void setUseTLS(boolean useTLS) { super.setUseTLS(useTLS); - if (traceDispatcher != null && traceDispatcher instanceof AsyncTraceDispatcher) { + if (traceDispatcher instanceof AsyncTraceDispatcher) { ((AsyncTraceDispatcher) traceDispatcher).getTraceProducer().setUseTLS(useTLS); } } - + /** * Start this producer instance.

* @@ -285,7 +301,7 @@ public void start() throws MQClientException { try { traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel()); } catch (MQClientException e) { - log.warn("trace dispatcher start failed ", e); + logger.warn("trace dispatcher start failed ", e); } } } @@ -317,7 +333,7 @@ public List fetchPublishMessageQueues(String topic) throws MQClien * 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 potentially + * {@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. @@ -580,7 +596,7 @@ public void send(Message msg, MessageQueueSelector selector, Object arg, SendCal * 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 potentially + * {@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 @@ -753,30 +769,32 @@ public TransactionSendResult sendMessageInTransaction(Message msg, /** * This method will be removed in a certain version after April 5, 2020, so please do not use this method. * - * @param key accesskey + * @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) throws MQClientException { - createTopic(key, withNamespace(newTopic), queueNum, 0); + 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 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) throws MQClientException { + public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, Map attributes) throws MQClientException { this.defaultMQProducerImpl.createTopic(key, withNamespace(newTopic), queueNum, topicSysFlag); } @@ -824,7 +842,7 @@ public long minOffset(MessageQueue mq) throws MQClientException { } /** - * Query earliest message store time. + * 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. * @@ -896,9 +914,8 @@ public QueryResult queryMessage(String topic, String key, int maxNum, long begin public MessageExt viewMessage(String topic, String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { try { - MessageId oldMsgId = MessageDecoder.decodeMessageId(msgId); return this.viewMessage(msgId); - } catch (Exception e) { + } catch (Exception ignored) { } return this.defaultMQProducerImpl.queryMessageByUniqKey(withNamespace(topic), msgId); } @@ -951,8 +968,7 @@ public void send(Collection msgs, MessageQueue mq, } /** - * Sets an Executor to be used for executing callback methods. If the Executor is not set, {@link - * NettyRemotingClient#publicExecutor} will be used. + * Sets an Executor to be used for executing callback methods. * * @param callbackExecutor the instance of Executor */ @@ -961,8 +977,7 @@ public void setCallbackExecutor(final ExecutorService callbackExecutor) { } /** - * Sets an Executor to be used for executing asynchronous send. If the Executor is not set, {@link - * DefaultMQProducerImpl#defaultAsyncSenderExecutor} will be used. + * Sets an Executor to be used for executing asynchronous send. * * @param asyncSenderExecutor the instance of Executor */ @@ -988,6 +1003,7 @@ private MessageBatch batch(Collection msgs) throws MQClientException { 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); @@ -1112,4 +1128,31 @@ public TraceDispatcher getTraceDispatcher() { public Set getRetryResponseCodes() { return retryResponseCodes; } + + public boolean isEnableBackpressureForAsyncMode() { + return enableBackpressureForAsyncMode; + } + + public void setEnableBackpressureForAsyncMode(boolean enableBackpressureForAsyncMode) { + this.enableBackpressureForAsyncMode = enableBackpressureForAsyncMode; + } + + public int getBackPressureForAsyncSendNum() { + return backPressureForAsyncSendNum; + } + + public void setBackPressureForAsyncSendNum(int backPressureForAsyncSendNum) { + this.backPressureForAsyncSendNum = backPressureForAsyncSendNum; + defaultMQProducerImpl.setSemaphoreAsyncSendNum(backPressureForAsyncSendNum); + } + + public int getBackPressureForAsyncSendSize() { + return backPressureForAsyncSendSize; + } + + public void setBackPressureForAsyncSendSize(int backPressureForAsyncSendSize) { + this.backPressureForAsyncSendSize = backPressureForAsyncSendSize; + defaultMQProducerImpl.setSemaphoreAsyncSendSize(backPressureForAsyncSendSize); + } + } diff --git a/client/src/main/java/org/apache/rocketmq/client/producer/RequestFutureHolder.java b/client/src/main/java/org/apache/rocketmq/client/producer/RequestFutureHolder.java index df0706f1f8f..00f5bb6e6ea 100644 --- a/client/src/main/java/org/apache/rocketmq/client/producer/RequestFutureHolder.java +++ b/client/src/main/java/org/apache/rocketmq/client/producer/RequestFutureHolder.java @@ -31,15 +31,15 @@ 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.client.log.ClientLogger; import org.apache.rocketmq.common.ThreadFactoryImpl; -import org.apache.rocketmq.logging.InternalLogger; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class RequestFutureHolder { - private static InternalLogger log = ClientLogger.getLog(); + 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 ConcurrentHashMap requestFutureTable = new ConcurrentHashMap<>(); + private final Set producerSet = new HashSet<>(); private ScheduledExecutorService scheduledExecutorService = null; public ConcurrentHashMap getRequestFutureTable() { @@ -47,7 +47,7 @@ public ConcurrentHashMap getRequestFutureTable() } private void scanExpiredRequest() { - final List rfList = new LinkedList(); + final List rfList = new LinkedList<>(); Iterator> it = requestFutureTable.entrySet().iterator(); while (it.hasNext()) { Map.Entry next = it.next(); diff --git a/client/src/main/java/org/apache/rocketmq/client/producer/SendResult.java b/client/src/main/java/org/apache/rocketmq/client/producer/SendResult.java index 80948830fba..dd7ea1cdc5f 100644 --- a/client/src/main/java/org/apache/rocketmq/client/producer/SendResult.java +++ b/client/src/main/java/org/apache/rocketmq/client/producer/SendResult.java @@ -28,6 +28,7 @@ public class SendResult { private String offsetMsgId; private String regionId; private boolean traceOn = true; + private byte[] rawRespBody; public SendResult() { } @@ -130,4 +131,12 @@ public String toString() { return "SendResult [sendStatus=" + sendStatus + ", msgId=" + msgId + ", offsetMsgId=" + offsetMsgId + ", messageQueue=" + messageQueue + ", queueOffset=" + queueOffset + "]"; } + + public void setRawRespBody(byte[] body) { + this.rawRespBody = body; + } + + public byte[] getRawRespBody() { + return rawRespBody; + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/producer/TransactionMQProducer.java b/client/src/main/java/org/apache/rocketmq/client/producer/TransactionMQProducer.java index 4eb758df401..baa8b440805 100644 --- a/client/src/main/java/org/apache/rocketmq/client/producer/TransactionMQProducer.java +++ b/client/src/main/java/org/apache/rocketmq/client/producer/TransactionMQProducer.java @@ -19,8 +19,8 @@ import java.util.concurrent.ExecutorService; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.Message; -import org.apache.rocketmq.common.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.RPCHook; +import org.apache.rocketmq.remoting.protocol.NamespaceUtil; public class TransactionMQProducer extends DefaultMQProducer { private TransactionCheckListener transactionCheckListener; diff --git a/client/src/main/java/org/apache/rocketmq/client/stat/ConsumerStatsManager.java b/client/src/main/java/org/apache/rocketmq/client/stat/ConsumerStatsManager.java index ba4773ae679..a9f506e7600 100644 --- a/client/src/main/java/org/apache/rocketmq/client/stat/ConsumerStatsManager.java +++ b/client/src/main/java/org/apache/rocketmq/client/stat/ConsumerStatsManager.java @@ -18,15 +18,14 @@ package org.apache.rocketmq.client.stat; import java.util.concurrent.ScheduledExecutorService; - -import org.apache.rocketmq.client.log.ClientLogger; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.common.protocol.body.ConsumeStatus; 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 InternalLogger log = ClientLogger.getLog(); + 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"; diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/AsyncTraceDispatcher.java b/client/src/main/java/org/apache/rocketmq/client/trace/AsyncTraceDispatcher.java index 7ff8bd77e03..ea423b71766 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/AsyncTraceDispatcher.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/AsyncTraceDispatcher.java @@ -16,11 +16,11 @@ */ 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.UUID; import java.util.concurrent.ArrayBlockingQueue; @@ -29,15 +29,12 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; - -import org.apache.commons.lang3.StringUtils; 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.log.ClientLogger; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.SendCallback; @@ -47,24 +44,28 @@ 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.logging.InternalLogger; import org.apache.rocketmq.remoting.RPCHook; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import static org.apache.rocketmq.client.trace.TraceConstants.TRACE_INSTANCE_NAME; public class AsyncTraceDispatcher implements TraceDispatcher { - - private final static InternalLogger log = ClientLogger.getLog(); + private final static Logger log = LoggerFactory.getLogger(AsyncTraceDispatcher.class); private final static AtomicInteger COUNTER = new AtomicInteger(); + private final static short MAX_MSG_KEY_SIZE = Short.MAX_VALUE - 10000; private final int queueSize; private final int batchSize; private final int maxMsgSize; + private final long pollingTimeMil; + private final long waitTimeThresholdMil; private final DefaultMQProducer traceProducer; private final ThreadPoolExecutor traceExecutor; // The last discard number of log private AtomicLong discardCount; private Thread worker; private final ArrayBlockingQueue traceContextQueue; + private final HashMap taskQueueByTopic; private ArrayBlockingQueue appenderQueue; private volatile Thread shutDownHook; private volatile boolean stopped = false; @@ -72,9 +73,9 @@ public class AsyncTraceDispatcher implements TraceDispatcher { private DefaultMQPushConsumerImpl hostConsumer; private volatile ThreadLocalIndex sendWhichQueue = new ThreadLocalIndex(); private String dispatcherId = UUID.randomUUID().toString(); - private String traceTopicName; + private volatile String traceTopicName; private AtomicBoolean isStarted = new AtomicBoolean(false); - private AccessChannel accessChannel = AccessChannel.LOCAL; + private volatile AccessChannel accessChannel = AccessChannel.LOCAL; private String group; private Type type; @@ -83,24 +84,27 @@ public AsyncTraceDispatcher(String group, Type type, String traceTopicName, RPCH this.queueSize = 2048; this.batchSize = 100; this.maxMsgSize = 128000; + this.pollingTimeMil = 100; + this.waitTimeThresholdMil = 500; this.discardCount = new AtomicLong(0L); - this.traceContextQueue = new ArrayBlockingQueue(1024); + this.traceContextQueue = new ArrayBlockingQueue<>(1024); + this.taskQueueByTopic = new HashMap(); this.group = group; this.type = type; - this.appenderQueue = new ArrayBlockingQueue(queueSize); + this.appenderQueue = new ArrayBlockingQueue<>(queueSize); if (!UtilAll.isBlank(traceTopicName)) { this.traceTopicName = traceTopicName; } else { this.traceTopicName = TopicValidator.RMQ_SYS_TRACE_TOPIC; } this.traceExecutor = new ThreadPoolExecutor(// - 10, // - 20, // - 1000 * 60, // - TimeUnit.MILLISECONDS, // - this.appenderQueue, // - new ThreadFactoryImpl("MQTraceSendThread_")); + 10, // + 20, // + 1000 * 60, // + TimeUnit.MILLISECONDS, // + this.appenderQueue, // + new ThreadFactoryImpl("MQTraceSendThread_")); traceProducer = getAndCreateTraceProducer(rpcHook); } @@ -161,7 +165,7 @@ private DefaultMQProducer getAndCreateTraceProducer(RPCHook rpcHook) { traceProducerInstance.setSendMsgTimeout(5000); traceProducerInstance.setVipChannelEnabled(false); // The max size of message is 128K - traceProducerInstance.setMaxMessageSize(maxMsgSize - 10 * 1000); + traceProducerInstance.setMaxMessageSize(maxMsgSize); } return traceProducerInstance; } @@ -184,6 +188,11 @@ public void flush() { // The maximum waiting time for refresh,avoid being written all the time, resulting in failure to return. long end = System.currentTimeMillis() + 500; while (System.currentTimeMillis() <= end) { + synchronized (taskQueueByTopic) { + for (TraceDataSegment taskInfo : taskQueueByTopic.values()) { + taskInfo.sendAllData(); + } + } synchronized (traceContextQueue) { if (traceContextQueue.size() == 0 && appenderQueue.size() == 0) { break; @@ -243,113 +252,140 @@ class AsyncRunnable implements Runnable { @Override public void run() { while (!stopped) { - List contexts = new ArrayList(batchSize); synchronized (traceContextQueue) { - for (int i = 0; i < batchSize; i++) { - TraceContext context = null; + long endTime = System.currentTimeMillis() + pollingTimeMil; + while (System.currentTimeMillis() < endTime) { try { - //get trace data element from blocking Queue - traceContextQueue - context = traceContextQueue.poll(5, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - } - if (context != null) { - contexts.add(context); - } else { - break; + TraceContext traceContext = traceContextQueue.poll( + endTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS + ); + + if (traceContext != null && !traceContext.getTraceBeans().isEmpty()) { + // get the topic which the trace message will send to + String traceTopicName = this.getTraceTopicName(traceContext.getRegionId()); + + // get the traceDataSegment which will save this trace message, create if null + TraceDataSegment traceDataSegment = taskQueueByTopic.get(traceTopicName); + if (traceDataSegment == null) { + traceDataSegment = new TraceDataSegment(traceTopicName, traceContext.getRegionId()); + taskQueueByTopic.put(traceTopicName, traceDataSegment); + } + + // encode traceContext and save it into traceDataSegment + // NOTE if data size in traceDataSegment more than maxMsgSize, + // a AsyncDataSendTask will be created and submitted + TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(traceContext); + traceDataSegment.addTraceTransferBean(traceTransferBean); + } + } catch (InterruptedException ignore) { + log.debug("traceContextQueue#poll exception"); } } - if (contexts.size() > 0) { - AsyncAppenderRequest request = new AsyncAppenderRequest(contexts); - traceExecutor.submit(request); - } else if (AsyncTraceDispatcher.this.stopped) { + + // NOTE send the data in traceDataSegment which the first TraceTransferBean + // is longer than waitTimeThreshold + sendDataByTimeThreshold(); + + if (AsyncTraceDispatcher.this.stopped) { this.stopped = true; } } } } - } - class AsyncAppenderRequest implements Runnable { - List contextList; + private void sendDataByTimeThreshold() { + long now = System.currentTimeMillis(); + for (TraceDataSegment taskInfo : taskQueueByTopic.values()) { + if (now - taskInfo.firstBeanAddTime >= waitTimeThresholdMil) { + taskInfo.sendAllData(); + } + } + } - public AsyncAppenderRequest(final List contextList) { - if (contextList != null) { - this.contextList = contextList; - } else { - this.contextList = new ArrayList(1); + private String getTraceTopicName(String regionId) { + AccessChannel accessChannel = AsyncTraceDispatcher.this.getAccessChannel(); + if (AccessChannel.CLOUD == accessChannel) { + return TraceConstants.TRACE_TOPIC_PREFIX + regionId; } + + return AsyncTraceDispatcher.this.getTraceTopicName(); } + } - @Override - public void run() { - sendTraceData(contextList); + class TraceDataSegment { + private long firstBeanAddTime; + private int currentMsgSize; + private int currentMsgKeySize; + private final String traceTopicName; + private final String regionId; + private final List traceTransferBeanList = new ArrayList(); + + TraceDataSegment(String traceTopicName, String regionId) { + this.traceTopicName = traceTopicName; + this.regionId = regionId; } - public void sendTraceData(List contextList) { - Map> transBeanMap = new HashMap>(); - for (TraceContext context : contextList) { - if (context.getTraceBeans().isEmpty()) { - continue; - } - // Topic value corresponding to original message entity content - String topic = context.getTraceBeans().get(0).getTopic(); - String regionId = context.getRegionId(); - // Use original message entity's topic as key - String key = topic; - if (!StringUtils.isBlank(regionId)) { - key = key + TraceConstants.CONTENT_SPLITOR + regionId; - } - List transBeanList = transBeanMap.get(key); - if (transBeanList == null) { - transBeanList = new ArrayList(); - transBeanMap.put(key, transBeanList); - } - TraceTransferBean traceData = TraceDataEncoder.encoderFromContextBean(context); - transBeanList.add(traceData); - } - for (Map.Entry> entry : transBeanMap.entrySet()) { - String[] key = entry.getKey().split(String.valueOf(TraceConstants.CONTENT_SPLITOR)); - String dataTopic = entry.getKey(); - String regionId = null; - if (key.length > 1) { - dataTopic = key[0]; - regionId = key[1]; - } - flushData(entry.getValue(), dataTopic, regionId); + public void addTraceTransferBean(TraceTransferBean traceTransferBean) { + initFirstBeanAddTime(); + this.traceTransferBeanList.add(traceTransferBean); + this.currentMsgSize += traceTransferBean.getTransData().length(); + + this.currentMsgKeySize = traceTransferBean.getTransKey().stream() + .reduce(currentMsgKeySize, (acc, x) -> acc + x.length(), Integer::sum); + if (currentMsgSize >= traceProducer.getMaxMessageSize() - 10 * 1000 || currentMsgKeySize >= MAX_MSG_KEY_SIZE) { + List dataToSend = new ArrayList(traceTransferBeanList); + AsyncDataSendTask asyncDataSendTask = new AsyncDataSendTask(traceTopicName, regionId, dataToSend); + traceExecutor.submit(asyncDataSendTask); + this.clear(); } } - /** - * Batch sending data actually - */ - private void flushData(List transBeanList, String dataTopic, String regionId) { - if (transBeanList.size() == 0) { + public void sendAllData() { + if (this.traceTransferBeanList.isEmpty()) { return; } - // Temporary buffer - StringBuilder buffer = new StringBuilder(1024); - int count = 0; - Set keySet = new HashSet(); + List dataToSend = new ArrayList(traceTransferBeanList); + AsyncDataSendTask asyncDataSendTask = new AsyncDataSendTask(traceTopicName, regionId, dataToSend); + traceExecutor.submit(asyncDataSendTask); + + this.clear(); + } + + private void initFirstBeanAddTime() { + if (firstBeanAddTime == 0) { + firstBeanAddTime = System.currentTimeMillis(); + } + } + + private void clear() { + this.firstBeanAddTime = 0; + this.currentMsgSize = 0; + this.currentMsgKeySize = 0; + this.traceTransferBeanList.clear(); + } + } - for (TraceTransferBean bean : transBeanList) { - // Keyset of message trace includes msgId of or original message + class AsyncDataSendTask implements Runnable { + private final String traceTopicName; + private final String regionId; + private final List traceTransferBeanList; + + public AsyncDataSendTask(String traceTopicName, String regionId, List traceTransferBeanList) { + this.traceTopicName = traceTopicName; + this.regionId = regionId; + this.traceTransferBeanList = traceTransferBeanList; + } + + @Override + public void run() { + StringBuilder buffer = new StringBuilder(1024); + Set keySet = new HashSet<>(); + for (TraceTransferBean bean : traceTransferBeanList) { keySet.addAll(bean.getTransKey()); buffer.append(bean.getTransData()); - count++; - // Ensure that the size of the package should not exceed the upper limit. - if (buffer.length() >= traceProducer.getMaxMessageSize()) { - sendTraceDataByMQ(keySet, buffer.toString(), dataTopic, regionId); - // Clear temporary buffer after finishing - buffer.delete(0, buffer.length()); - keySet.clear(); - count = 0; - } - } - if (count > 0) { - sendTraceDataByMQ(keySet, buffer.toString(), dataTopic, regionId); } - transBeanList.clear(); + sendTraceDataByMQ(keySet, buffer.toString(), traceTopicName); } /** @@ -357,13 +393,10 @@ private void flushData(List transBeanList, String dataTopic, * * @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 dataTopic, String regionId) { - String traceTopic = traceTopicName; - if (AccessChannel.CLOUD == accessChannel) { - traceTopic = TraceConstants.TRACE_TOPIC_PREFIX + regionId; - } - final Message message = new Message(traceTopic, data.getBytes()); + 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 { @@ -387,17 +420,14 @@ public void onException(Throwable e) { @Override public MessageQueue select(List mqs, Message msg, Object arg) { Set brokerSet = (Set) arg; - List filterMqs = new ArrayList(); + List filterMqs = new ArrayList<>(); for (MessageQueue queue : mqs) { if (brokerSet.contains(queue.getBrokerName())) { filterMqs.add(queue); } } int index = sendWhichQueue.incrementAndGet(); - int pos = Math.abs(index) % filterMqs.size(); - if (pos < 0) { - pos = 0; - } + int pos = index % filterMqs.size(); return filterMqs.get(pos); } }, traceBrokerSet, callback); @@ -409,11 +439,11 @@ public MessageQueue select(List mqs, Message msg, Object arg) { } private Set tryGetMessageQueueBrokerSet(DefaultMQProducerImpl producer, String topic) { - Set brokerSet = new HashSet(); + 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); + producer.getMqClientFactory().updateTopicRouteInfoFromNameServer(topic); topicPublishInfo = producer.getTopicPublishInfoTable().get(topic); } if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) { diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/TraceContext.java b/client/src/main/java/org/apache/rocketmq/client/trace/TraceContext.java index f61ba888cb3..96dc1df1858 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/TraceContext.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/TraceContext.java @@ -118,19 +118,20 @@ public void setRegionName(String regionName) { @Override public int compareTo(TraceContext o) { - return (int) (this.timeStamp - o.getTimeStamp()); + return Long.compare(this.timeStamp, o.getTimeStamp()); } @Override public String toString() { StringBuilder sb = new StringBuilder(1024); - sb.append(traceType).append("_").append(groupName) - .append("_").append(regionId).append("_").append(isSuccess).append("_"); + 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() + "_" + bean.getTopic() + "_"); + sb.append(bean.getMsgId()).append("_").append(bean.getTopic()).append("_"); } } - return "TraceContext{" + sb.toString() + '}'; + sb.append('}'); + return sb.toString(); } } diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java b/client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java index f0c685e0a64..91842226426 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java @@ -36,7 +36,7 @@ public class TraceDataEncoder { * @return */ public static List decoderFromTraceDataString(String traceData) { - List resList = new ArrayList(); + List resList = new ArrayList<>(); if (traceData == null || traceData.length() <= 0) { return resList; } @@ -73,7 +73,7 @@ public static List decoderFromTraceDataString(String traceData) { bean.setClientHost(line[14]); } - pubContext.setTraceBeans(new ArrayList(1)); + pubContext.setTraceBeans(new ArrayList<>(1)); pubContext.getTraceBeans().add(bean); resList.add(pubContext); } else if (line[0].equals(TraceType.SubBefore.name())) { @@ -87,7 +87,7 @@ public static List decoderFromTraceDataString(String traceData) { bean.setMsgId(line[5]); bean.setRetryTimes(Integer.parseInt(line[6])); bean.setKeys(line[7]); - subBeforeContext.setTraceBeans(new ArrayList(1)); + subBeforeContext.setTraceBeans(new ArrayList<>(1)); subBeforeContext.getTraceBeans().add(bean); resList.add(subBeforeContext); } else if (line[0].equals(TraceType.SubAfter.name())) { @@ -97,7 +97,7 @@ public static List decoderFromTraceDataString(String traceData) { TraceBean bean = new TraceBean(); bean.setMsgId(line[2]); bean.setKeys(line[5]); - subAfterContext.setTraceBeans(new ArrayList(1)); + subAfterContext.setTraceBeans(new ArrayList<>(1)); subAfterContext.getTraceBeans().add(bean); subAfterContext.setCostTime(Integer.parseInt(line[3])); subAfterContext.setSuccess(Boolean.parseBoolean(line[4])); @@ -128,7 +128,7 @@ public static List decoderFromTraceDataString(String traceData) { bean.setTransactionState(LocalTransactionState.valueOf(line[11])); bean.setFromTransactionCheck(Boolean.parseBoolean(line[12])); - endTransactionContext.setTraceBeans(new ArrayList(1)); + endTransactionContext.setTraceBeans(new ArrayList<>(1)); endTransactionContext.getTraceBeans().add(bean); resList.add(endTransactionContext); } @@ -146,7 +146,7 @@ public static TraceTransferBean encoderFromContextBean(TraceContext ctx) { if (ctx == null) { return null; } - //build message trace of the transfering entity content bean + //build message trace of the transferring entity content bean TraceTransferBean transferBean = new TraceTransferBean(); StringBuilder sb = new StringBuilder(256); switch (ctx.getTraceType()) { diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/TraceDispatcher.java b/client/src/main/java/org/apache/rocketmq/client/trace/TraceDispatcher.java index 33341cf6b8f..ac4ef3ba882 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/TraceDispatcher.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/TraceDispatcher.java @@ -34,8 +34,8 @@ enum Type { void start(String nameSrvAddr, AccessChannel accessChannel) throws MQClientException; /** - * Append the transfering data - * @param ctx data infomation + * Append the transferring data + * @param ctx data information * @return */ boolean append(Object ctx); diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/TraceTransferBean.java b/client/src/main/java/org/apache/rocketmq/client/trace/TraceTransferBean.java index 052ca365213..482a782e517 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/TraceTransferBean.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/TraceTransferBean.java @@ -20,11 +20,11 @@ import java.util.Set; /** - * Trace transfering bean + * Trace transferring bean */ public class TraceTransferBean { private String transData; - private Set transKey = new HashSet(); + private Set transKey = new HashSet<>(); public String getTransData() { return transData; diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/TraceView.java b/client/src/main/java/org/apache/rocketmq/client/trace/TraceView.java index 7601221cded..01ce56699d8 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/TraceView.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/TraceView.java @@ -39,7 +39,7 @@ public class TraceView { private String status; public static List decodeFromTraceTransData(String key, MessageExt messageExt) { - List messageTraceViewList = new ArrayList(); + List messageTraceViewList = new ArrayList<>(); String messageBody = new String(messageExt.getBody(), StandardCharsets.UTF_8); if (messageBody == null || messageBody.length() <= 0) { return messageTraceViewList; diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageOpenTracingHookImpl.java b/client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageOpenTracingHookImpl.java index fe97c773949..b983df30613 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageOpenTracingHookImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageOpenTracingHookImpl.java @@ -22,15 +22,14 @@ 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.common.protocol.NamespaceUtil; - -import java.util.ArrayList; -import java.util.List; +import org.apache.rocketmq.remoting.protocol.NamespaceUtil; public class ConsumeMessageOpenTracingHookImpl implements ConsumeMessageHook { @@ -51,7 +50,7 @@ public void consumeMessageBefore(ConsumeMessageContext context) { if (context == null || context.getMsgList() == null || context.getMsgList().isEmpty()) { return; } - List spanList = new ArrayList(); + List spanList = new ArrayList<>(); for (MessageExt msg : context.getMsgList()) { if (msg == null) { continue; diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageTraceHookImpl.java b/client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageTraceHookImpl.java index bce613987ff..6db8a177f36 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageTraceHookImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageTraceHookImpl.java @@ -16,21 +16,20 @@ */ 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.TraceBean; 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 java.util.ArrayList; -import java.util.List; -import org.apache.rocketmq.common.protocol.NamespaceUtil; +import org.apache.rocketmq.remoting.protocol.NamespaceUtil; public class ConsumeMessageTraceHookImpl implements ConsumeMessageHook { @@ -54,7 +53,7 @@ public void consumeMessageBefore(ConsumeMessageContext context) { context.setMqTraceContext(traceContext); traceContext.setTraceType(TraceType.SubBefore);// traceContext.setGroupName(NamespaceUtil.withoutNamespace(context.getConsumerGroup()));// - List beans = new ArrayList(); + List beans = new ArrayList<>(); for (MessageExt msg : context.getMsgList()) { if (msg == null) { continue; diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/hook/EndTransactionTraceHookImpl.java b/client/src/main/java/org/apache/rocketmq/client/trace/hook/EndTransactionTraceHookImpl.java index cbd755ba39e..d69388e0418 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/hook/EndTransactionTraceHookImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/hook/EndTransactionTraceHookImpl.java @@ -16,6 +16,7 @@ */ 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; @@ -27,9 +28,7 @@ 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.common.protocol.NamespaceUtil; - -import java.util.ArrayList; +import org.apache.rocketmq.remoting.protocol.NamespaceUtil; public class EndTransactionTraceHookImpl implements EndTransactionHook { @@ -53,7 +52,7 @@ public void endTransaction(EndTransactionContext context) { Message msg = context.getMessage(); //build the context content of TuxeTraceContext TraceContext tuxeContext = new TraceContext(); - tuxeContext.setTraceBeans(new ArrayList(1)); + tuxeContext.setTraceBeans(new ArrayList<>(1)); tuxeContext.setTraceType(TraceType.EndTransaction); tuxeContext.setGroupName(NamespaceUtil.withoutNamespace(context.getProducerGroup())); //build the data bean object of message trace @@ -63,7 +62,7 @@ public void endTransaction(EndTransactionContext context) { traceBean.setKeys(context.getMessage().getKeys()); traceBean.setStoreHost(context.getBrokerAddr()); traceBean.setMsgType(MessageType.Trans_msg_Commit); - traceBean.setClientHost(((AsyncTraceDispatcher)localDispatcher).getHostProducer().getmQClientFactory().getClientId()); + traceBean.setClientHost(((AsyncTraceDispatcher)localDispatcher).getHostProducer().getMqClientFactory().getClientId()); traceBean.setMsgId(context.getMsgId()); traceBean.setTransactionState(context.getTransactionState()); traceBean.setTransactionId(context.getTransactionId()); diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/hook/SendMessageTraceHookImpl.java b/client/src/main/java/org/apache/rocketmq/client/trace/hook/SendMessageTraceHookImpl.java index 80c7babdaa6..dba04b593f2 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/hook/SendMessageTraceHookImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/hook/SendMessageTraceHookImpl.java @@ -25,7 +25,7 @@ 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.protocol.NamespaceUtil; +import org.apache.rocketmq.remoting.protocol.NamespaceUtil; public class SendMessageTraceHookImpl implements SendMessageHook { @@ -46,12 +46,12 @@ public void sendMessageBefore(SendMessageContext context) { if (context == null || context.getMessage().getTopic().startsWith(((AsyncTraceDispatcher) localDispatcher).getTraceTopicName())) { return; } - //build the context content of TuxeTraceContext - TraceContext tuxeContext = new TraceContext(); - tuxeContext.setTraceBeans(new ArrayList(1)); - context.setMqTraceContext(tuxeContext); - tuxeContext.setTraceType(TraceType.Pub); - tuxeContext.setGroupName(NamespaceUtil.withoutNamespace(context.getProducerGroup())); + //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())); @@ -60,7 +60,7 @@ public void sendMessageBefore(SendMessageContext context) { traceBean.setStoreHost(context.getBrokerAddr()); traceBean.setBodyLength(context.getMessage().getBody().length); traceBean.setMsgType(context.getMsgType()); - tuxeContext.getTraceBeans().add(traceBean); + traceContext.getTraceBeans().add(traceBean); } @Override @@ -80,19 +80,19 @@ public void sendMessageAfter(SendMessageContext context) { return; } - TraceContext tuxeContext = (TraceContext) context.getMqTraceContext(); - TraceBean traceBean = tuxeContext.getTraceBeans().get(0); - int costTime = (int) ((System.currentTimeMillis() - tuxeContext.getTimeStamp()) / tuxeContext.getTraceBeans().size()); - tuxeContext.setCostTime(costTime); + 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)) { - tuxeContext.setSuccess(true); + traceContext.setSuccess(true); } else { - tuxeContext.setSuccess(false); + traceContext.setSuccess(false); } - tuxeContext.setRegionId(context.getSendResult().getRegionId()); + traceContext.setRegionId(context.getSendResult().getRegionId()); traceBean.setMsgId(context.getSendResult().getMsgId()); traceBean.setOffsetMsgId(context.getSendResult().getOffsetMsgId()); - traceBean.setStoreTime(tuxeContext.getTimeStamp() + costTime / 2); - localDispatcher.append(tuxeContext); + traceBean.setStoreTime(traceContext.getTimeStamp() + costTime / 2); + localDispatcher.append(traceContext); } } diff --git a/client/src/main/resources/rmq.client.logback.xml b/client/src/main/resources/rmq.client.logback.xml new file mode 100644 index 00000000000..8e57d8d33ec --- /dev/null +++ b/client/src/main/resources/rmq.client.logback.xml @@ -0,0 +1,55 @@ + + + + + + + + %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 + + + + + + + + + + + + diff --git a/client/src/test/java/org/apache/rocketmq/client/ValidatorsTest.java b/client/src/test/java/org/apache/rocketmq/client/ValidatorsTest.java index aa448dc07fb..b00c5d1450a 100644 --- a/client/src/test/java/org/apache/rocketmq/client/ValidatorsTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/ValidatorsTest.java @@ -17,9 +17,13 @@ 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; @@ -28,6 +32,16 @@ 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"); @@ -96,4 +110,63 @@ public void testIsNotAllowedSendTopic() { } } } + + @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"))); + } + } } diff --git a/client/src/test/java/org/apache/rocketmq/client/common/ThreadLocalIndexTest.java b/client/src/test/java/org/apache/rocketmq/client/common/ThreadLocalIndexTest.java index de35b9181b6..94f02abaae4 100644 --- a/client/src/test/java/org/apache/rocketmq/client/common/ThreadLocalIndexTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/common/ThreadLocalIndexTest.java @@ -16,6 +16,7 @@ */ package org.apache.rocketmq.client.common; +import java.lang.reflect.Field; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -33,7 +34,21 @@ public void testIncrementAndGet() throws Exception { public void testIncrementAndGet2() throws Exception { ThreadLocalIndex localIndex = new ThreadLocalIndex(); int initialVal = localIndex.incrementAndGet(); - assertThat(initialVal >= 0); + 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(); } } \ No newline at end of file diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java index 227ea441719..24e39f56689 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java @@ -20,7 +20,9 @@ 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; @@ -30,6 +32,7 @@ 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; @@ -49,10 +52,11 @@ 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.protocol.header.PullMessageRequestHeader; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.remoting.RPCHook; +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; @@ -63,6 +67,7 @@ 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; @@ -78,6 +83,8 @@ public class DefaultLitePullConsumerTest { @Spy private MQClientInstance mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(new ClientConfig()); + private MQClientInstance mqClientInstance; + @Mock private MQClientAPIImpl mQClientAPIImpl; @Mock @@ -93,12 +100,15 @@ public class DefaultLitePullConsumerTest { 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); - for (Map.Entry entry : factoryTable.entrySet()) { - entry.getValue().shutdown(); - } + factoryTable.forEach((s, instance) -> instance.shutdown()); factoryTable.clear(); Field field = MQClientInstance.class.getDeclaredField("rebalanceService"); @@ -107,6 +117,18 @@ public void init() throws Exception { 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 @@ -123,11 +145,78 @@ public void testAssign_PollMessageSuccess() throws Exception { } } + @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(); + Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createMessageQueue()); litePullConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); litePullConsumer.setPollTimeoutMillis(20 * 1000); @@ -143,7 +232,7 @@ public void testSubscribe_PollMessageSuccess() throws Exception { public void testSubscribe_BroadcastPollMessageSuccess() throws Exception { DefaultLitePullConsumer litePullConsumer = createBroadcastLitePullConsumer(); try { - Set messageQueueSet = new HashSet(); + Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createMessageQueue()); litePullConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); litePullConsumer.setPollTimeoutMillis(20 * 1000); @@ -191,6 +280,7 @@ public void testSeek_SeekOffsetSuccess() throws Exception { 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"); @@ -209,6 +299,7 @@ public void testSeek_SeekToBegin() throws Exception { 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); @@ -226,6 +317,7 @@ public void testSeek_SeekToEnd() throws Exception { 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); @@ -243,6 +335,7 @@ public void testSeek_SeekOffsetIllegal() throws Exception { List messageQueues = Collections.singletonList(messageQueue); litePullConsumer.assign(messageQueues); litePullConsumer.pause(messageQueues); + litePullConsumer.pause(Collections.singletonList(messageQueue)); try { litePullConsumer.seek(messageQueue, -1); failBecauseExceptionWasNotThrown(MQClientException.class); @@ -296,8 +389,12 @@ public void testOffsetForTimestamp_FailedAndSuccess() throws Exception { } doReturn(123L).when(mQAdminImpl).searchOffset(any(MessageQueue.class), anyLong()); litePullConsumer = createStartLitePullConsumer(); - long offset = litePullConsumer.offsetForTimestamp(messageQueue, 123456L); - assertThat(offset).isEqualTo(123L); + try { + long offset = litePullConsumer.offsetForTimestamp(messageQueue, 123456L); + assertThat(offset).isEqualTo(123L); + } finally { + litePullConsumer.shutdown(); + } } @Test @@ -371,18 +468,23 @@ public void testPullTaskImpl_ProcessQueueDropped() throws Exception { public void testRegisterTopicMessageQueueChangeListener_Success() throws Exception { flag = false; DefaultLitePullConsumer litePullConsumer = createStartLitePullConsumer(); - 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(); + 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 @@ -486,11 +588,15 @@ public void testCheckConfig_Exception() { @Test public void testComputePullFromWhereReturnedNotFound() 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(-1L); - long offset = rebalanceImpl.computePullFromWhere(messageQueue); - assertThat(offset).isEqualTo(0); + 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 @@ -501,6 +607,7 @@ public void testComputePullFromWhereReturned() throws Exception { when(offsetStore.readOffset(any(MessageQueue.class), any(ReadOffsetType.class))).thenReturn(100L); long offset = rebalanceImpl.computePullFromWhere(messageQueue); assertThat(offset).isEqualTo(100); + defaultLitePullConsumer.shutdown(); } @Test @@ -512,18 +619,23 @@ public void testComputePullFromLast() throws Exception { 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(); - 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); + 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 @@ -537,26 +649,54 @@ public void testConsumerAfterShutdown() throws Exception { 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(new Runnable() { - @Override - public void run() { - while (consumer.isRunning()) { - List poll = consumer.poll(2 * 1000); - } + 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(); @@ -598,7 +738,7 @@ public PullResult answer(InvocationOnMock mock) throws Throwable { messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); - PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); + PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); return pullResult; } }); @@ -610,6 +750,65 @@ public PullResult answer(InvocationOnMock mock) throws Throwable { 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"); @@ -620,6 +819,23 @@ private DefaultLitePullConsumer createSubscribeLitePullConsumer() throws Excepti 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"); @@ -629,6 +845,16 @@ private DefaultLitePullConsumer createStartLitePullConsumer() throws Exception { 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; @@ -662,13 +888,12 @@ private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, P return new PullResultExt(pullStatus, requestHeader.getQueueOffset() + messageExtList.size(), 123, 2048, messageExtList, 0, outputStream.toByteArray()); } - private static void suppressUpdateTopicRouteInfoFromNameServer( + private 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()); diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPullConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPullConsumerTest.java index 7afaf2bea05..31788ac998c 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPullConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPullConsumerTest.java @@ -30,7 +30,7 @@ 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.common.protocol.header.PullMessageRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -106,7 +106,7 @@ public Object answer(InvocationOnMock mock) throws Throwable { assertThat(pullResult.getNextBeginOffset()).isEqualTo(1024 + 1); assertThat(pullResult.getMinOffset()).isEqualTo(123); assertThat(pullResult.getMaxOffset()).isEqualTo(2048); - assertThat(pullResult.getMsgFoundList()).isEqualTo(new ArrayList()); + assertThat(pullResult.getMsgFoundList()).isEqualTo(new ArrayList<>()); } @Test @@ -115,7 +115,7 @@ public void testPullMessage_NotFound() throws Exception { @Override public Object answer(InvocationOnMock mock) throws Throwable { PullMessageRequestHeader requestHeader = mock.getArgument(1); - return createPullResult(requestHeader, PullStatus.NO_NEW_MSG, new ArrayList()); + return createPullResult(requestHeader, PullStatus.NO_NEW_MSG, new ArrayList<>()); } }).when(mQClientAPIImpl).pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class)); @@ -147,7 +147,7 @@ public void onSuccess(PullResult pullResult) { assertThat(pullResult.getNextBeginOffset()).isEqualTo(1024 + 1); assertThat(pullResult.getMinOffset()).isEqualTo(123); assertThat(pullResult.getMaxOffset()).isEqualTo(2048); - assertThat(pullResult.getMsgFoundList()).isEqualTo(new ArrayList()); + assertThat(pullResult.getMsgFoundList()).isEqualTo(new ArrayList<>()); } @Override @@ -161,4 +161,4 @@ private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, P List messageExtList) throws Exception { return new PullResultExt(pullStatus, requestHeader.getQueueOffset() + messageExtList.size(), 123, 2048, messageExtList, 0, new byte[] {}); } -} \ No newline at end of file +} diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java index bfc87f13eaa..3943b922899 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java @@ -17,6 +17,7 @@ package org.apache.rocketmq.client.consumer; import java.io.ByteArrayOutputStream; +import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashSet; @@ -27,6 +28,7 @@ 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 org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; @@ -45,19 +47,20 @@ 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.consumer.RebalancePushImpl; 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.common.protocol.header.PullMessageRequestHeader; 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.Assert; import org.junit.Before; @@ -72,6 +75,7 @@ import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; 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; @@ -79,18 +83,20 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) +@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 RebalancePushImpl rebalancePushImpl; private static DefaultMQPushConsumer pushConsumer; + private AtomicLong queueOffset = new AtomicLong(1024); @Before public void init() throws Exception { @@ -120,10 +126,12 @@ public PullResult answer(InvocationOnMock mock) throws Throwable { } }); + 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 @@ -134,7 +142,6 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, }); DefaultMQPushConsumerImpl pushConsumerImpl = pushConsumer.getDefaultMQPushConsumerImpl(); - rebalancePushImpl = spy(new RebalancePushImpl(pushConsumer.getDefaultMQPushConsumerImpl())); // suppress updateTopicRouteInfoFromNameServer pushConsumer.changeInstanceNameToPID(); @@ -143,19 +150,53 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, 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)); - FieldUtils.writeDeclaredField(pushConsumerImpl, "rebalanceImpl", rebalanceImpl, true); + Field field = DefaultMQPushConsumerImpl.class.getDeclaredField("rebalanceImpl"); + field.setAccessible(true); + field.set(pushConsumerImpl, rebalanceImpl); - Set messageQueueSet = new HashSet(); + 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 @@ -171,7 +212,7 @@ public void testStart_OffsetShouldNotNUllAfterStart() { @Test public void testPullMessage_Success() throws InterruptedException, RemotingException, MQBrokerException { final CountDownLatch countDownLatch = new CountDownLatch(1); - final AtomicReference messageAtomic = new AtomicReference(); + final AtomicReference messageAtomic = new AtomicReference<>(); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, @@ -188,13 +229,13 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); - assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); + assertThat(msg.getBody()).isEqualTo(msgBody); } - @Test + @Test(timeout = 20000) public void testPullMessage_SuccessWithOrderlyService() throws Exception { final CountDownLatch countDownLatch = new CountDownLatch(1); - final AtomicReference messageAtomic = new AtomicReference(); + final AtomicReference messageAtomic = new AtomicReference<>(); MessageListenerOrderly listenerOrderly = new MessageListenerOrderly() { @Override @@ -211,11 +252,11 @@ public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderly PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestLater(createPullRequest(), 100); - countDownLatch.await(10, TimeUnit.SECONDS); + countDownLatch.await(); MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); - assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); + assertThat(msg.getBody()).isEqualTo(msgBody); } @Test @@ -259,7 +300,7 @@ public void testCheckConfig() { } } - @Test + @Test(timeout = 20000) public void testGracefulShutdown() throws InterruptedException, RemotingException, MQBrokerException, MQClientException { final CountDownLatch countDownLatch = new CountDownLatch(1); pushConsumer.setAwaitTerminationMillisWhenShutdown(2000); @@ -268,6 +309,7 @@ public void testGracefulShutdown() throws InterruptedException, RemotingExceptio @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { + assertThat(msgs.get(0).getBody()).isEqualTo(msgBody); countDownLatch.countDown(); try { Thread.sleep(1000); @@ -281,7 +323,7 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); - assertThat(countDownLatch.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(countDownLatch.await(30, TimeUnit.SECONDS)).isTrue(); pushConsumer.shutdown(); assertThat(messageConsumedFlag.get()).isTrue(); @@ -302,7 +344,7 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, private PullRequest createPullRequest() { PullRequest pullRequest = new PullRequest(); pullRequest.setConsumerGroup(consumerGroup); - pullRequest.setNextOffset(1024); + pullRequest.setNextOffset(queueOffset.get()); MessageQueue messageQueue = new MessageQueue(); messageQueue.setBrokerName(brokerName); @@ -331,13 +373,15 @@ public void testPullMessage_ExceptionOccursWhenComputePullFromWhere() throws MQC 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; - } - })); + 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(); diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMachineRoomNearByTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMachineRoomNearByTest.java index 0d394c38231..6650f84a5dc 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMachineRoomNearByTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMachineRoomNearByTest.java @@ -36,11 +36,13 @@ public class AllocateMachineRoomNearByTest { private final String topic = "topic_test"; private final AllocateMachineRoomNearby.MachineRoomResolver machineRoomResolver = new AllocateMachineRoomNearby.MachineRoomResolver() { - @Override public String brokerDeployIn(MessageQueue messageQueue) { + @Override + public String brokerDeployIn(MessageQueue messageQueue) { return messageQueue.getBrokerName().split("-")[0]; } - @Override public String consumerDeployIn(String clientID) { + @Override + public String consumerDeployIn(String clientID) { return clientID.split("-")[0]; } }; @@ -54,10 +56,10 @@ public void init() { @Test public void test1() { - testWhenIDCSizeEquals(5,20,10, false); - testWhenIDCSizeEquals(5,20,20, false); - testWhenIDCSizeEquals(5,20,30, false); - testWhenIDCSizeEquals(5,20,0, false ); + testWhenIDCSizeEquals(5,20,10); + testWhenIDCSizeEquals(5,20,20); + testWhenIDCSizeEquals(5,20,30); + testWhenIDCSizeEquals(5,20,0); } @Test @@ -78,18 +80,18 @@ public void test3() { @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 + 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,false); + testWhenIDCSizeEquals(brokerIDCSize,queueSize,consumerSize); } else if (brokerIDCSize > consumerIDCSize) { - testWhenConsumerIDCIsLess(brokerIDCSize,brokerIDCSize- consumerIDCSize, queueSize, consumerSize, false); + testWhenConsumerIDCIsLess(brokerIDCSize,brokerIDCSize - consumerIDCSize, queueSize, consumerSize, false); } else { testWhenConsumerIDCIsMore(brokerIDCSize, consumerIDCSize - brokerIDCSize, queueSize, consumerSize, false); } @@ -99,49 +101,33 @@ else if (brokerIDCSize > consumerIDCSize) { - public void testWhenIDCSizeEquals(int IDCSize, int queueSize, int consumerSize, boolean print) { - if (print) { - System.out.println("Test : IDCSize = "+ IDCSize +"queueSize = " + queueSize +" consumerSize = " + consumerSize); - } - List cidAll = prepareConsumer(IDCSize, consumerSize); - List mqAll = prepareMQ(IDCSize, queueSize); - List resAll = new ArrayList(); + 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); - if (print) { - System.out.println("cid: "+currentID+"--> res :" +res); - } for (MessageQueue mq : res) { Assert.assertTrue(machineRoomResolver.brokerDeployIn(mq).equals(machineRoomResolver.consumerDeployIn(currentID))); } resAll.addAll(res); } Assert.assertTrue(hasAllocateAllQ(cidAll,mqAll,resAll)); - - if (print) { - System.out.println("-------------------------------------------------------------------"); - } } public void testWhenConsumerIDCIsMore(int brokerIDCSize, int consumerMore, int queueSize, int consumerSize, boolean print) { - if (print) { - System.out.println("Test : IDCSize = "+ brokerIDCSize +" queueSize = " + queueSize +" consumerSize = " + consumerSize); - } - Set brokerIDCWithConsumer = new TreeSet(); - List cidAll = prepareConsumer(brokerIDCSize +consumerMore, consumerSize); + 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(); + List resAll = new ArrayList<>(); for (String currentID : cidAll) { List res = allocateMessageQueueStrategy.allocate("Test-C-G",currentID,mqAll,cidAll); - if (print) { - System.out.println("cid: "+currentID+"--> res :" +res); - } for (MessageQueue mq : res) { - if (brokerIDCWithConsumer.contains(machineRoomResolver.brokerDeployIn(mq))) {//healthy idc, so only consumer in this idc should be allocated + 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))); } } @@ -149,32 +135,23 @@ public void testWhenConsumerIDCIsMore(int brokerIDCSize, int consumerMore, int q } Assert.assertTrue(hasAllocateAllQ(cidAll,mqAll,resAll)); - if (print) { - System.out.println("-------------------------------------------------------------------"); - } } public void testWhenConsumerIDCIsLess(int brokerIDCSize, int consumerIDCLess, int queueSize, int consumerSize, boolean print) { - if (print) { - System.out.println("Test : IDCSize = "+ brokerIDCSize +" queueSize = " + queueSize +" consumerSize = " + consumerSize); - } - Set healthyIDC = new TreeSet(); + 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>(); + 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 (print) { - System.out.println("cid: "+currentID+"--> res :" +res); - } - if ( !idc2Res.containsKey(currentIDC)) { - idc2Res.put(currentIDC, new ArrayList()); + if (!idc2Res.containsKey(currentIDC)) { + idc2Res.put(currentIDC, new ArrayList<>()); } idc2Res.get(currentIDC).addAll(res); resAll.addAll(res); @@ -187,14 +164,11 @@ public void testWhenConsumerIDCIsLess(int brokerIDCSize, int consumerIDCLess, in } Assert.assertTrue(hasAllocateAllQ(cidAll,mqAll,resAll)); - if (print) { - System.out.println("-------------------------------------------------------------------"); - } } private boolean hasAllocateAllQ(List cidAll,List mqAll, List allocatedResAll) { - if (cidAll.isEmpty()){ + if (cidAll.isEmpty()) { return allocatedResAll.isEmpty(); } return mqAll.containsAll(allocatedResAll) && allocatedResAll.containsAll(mqAll) && mqAll.size() == allocatedResAll.size(); @@ -202,35 +176,35 @@ private boolean hasAllocateAllQ(List cidAll,List mqAll, Li private List createConsumerIdList(String machineRoom, int size) { - List consumerIdList = new ArrayList(size); + List consumerIdList = new ArrayList<>(size); for (int i = 0; i < size; i++) { - consumerIdList.add(machineRoom +"-"+CID_PREFIX + String.valueOf(i)); + consumerIdList.add(machineRoom + "-" + CID_PREFIX + String.valueOf(i)); } return consumerIdList; } private List createMessageQueueList(String machineRoom, int size) { - List messageQueueList = new ArrayList(size); + List messageQueueList = new ArrayList<>(size); for (int i = 0; i < size; i++) { - MessageQueue mq = new MessageQueue(topic, machineRoom+"-brokerName", 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)); + 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)); + 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; } diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircleTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircleTest.java index 497766d9a6b..ee314bc684f 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircleTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircleTest.java @@ -33,7 +33,7 @@ public void testAllocateMessageQueueAveragelyByCircle() { List allocateQueues = new AllocateMessageQueueAveragelyByCircle().allocate("", "CID_PREFIX", messageQueueList, consumerIdList); Assert.assertEquals(0, allocateQueues.size()); - Map consumerAllocateQueue = new HashMap(consumerIdList.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()]; @@ -49,7 +49,7 @@ public void testAllocateMessageQueueAveragelyByCircle() { } private List createConsumerIdList(int size) { - List consumerIdList = new ArrayList(size); + List consumerIdList = new ArrayList<>(size); for (int i = 0; i < size; i++) { consumerIdList.add("CID_PREFIX" + i); } @@ -57,7 +57,7 @@ private List createConsumerIdList(int size) { } private List createMessageQueueList(int size) { - List messageQueueList = new ArrayList(size); + List messageQueueList = new ArrayList<>(size); for (int i = 0; i < size; i++) { MessageQueue mq = new MessageQueue("topic", "brokerName", i); messageQueueList.add(mq); diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyTest.java index b486106783f..498d9e240b4 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyTest.java @@ -37,7 +37,7 @@ public void testAllocateMessageQueueAveragely() { } private List createConsumerIdList(int size) { - List consumerIdList = new ArrayList(size); + List consumerIdList = new ArrayList<>(size); for (int i = 0; i < size; i++) { consumerIdList.add("CID_PREFIX" + i); } @@ -45,7 +45,7 @@ private List createConsumerIdList(int size) { } private List createMessageQueueList(int size) { - List messageQueueList = new ArrayList(size); + List messageQueueList = new ArrayList<>(size); for (int i = 0; i < size; i++) { MessageQueue mq = new MessageQueue("topic", "brokerName", i); messageQueueList.add(mq); diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByConfigTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByConfigTest.java new file mode 100644 index 00000000000..baae104e21a --- /dev/null +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByConfigTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} + diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByMachineRoomTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByMachineRoomTest.java new file mode 100644 index 00000000000..62a77759727 --- /dev/null +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueByMachineRoomTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} + diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueConsitentHashTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueConsitentHashTest.java index 98ce7b6eb27..261d6d65a68 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueConsitentHashTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueConsitentHashTest.java @@ -38,23 +38,12 @@ public void init() { topic = "topic_test"; } - public void printMessageQueue(List messageQueueList, String name) { - if (messageQueueList == null || messageQueueList.size() < 1) - return; - System.out.println(name + ".......................................start"); - for (MessageQueue messageQueue : messageQueueList) { - System.out.println(messageQueue); - } - System.out.println(name + ".......................................end"); - } - @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); - printMessageQueue(result, "testCurrentCIDNotExists"); Assert.assertEquals(result.size(), 0); } @@ -106,38 +95,34 @@ public void testAllocate(int queueSize, int consumerSize) { AllocateMessageQueueStrategy allocateMessageQueueConsistentHash = new AllocateMessageQueueConsistentHash(3); List mqAll = createMessageQueueList(queueSize); - //System.out.println("mqAll:" + mqAll.toString()); List cidAll = createConsumerIdList(consumerSize); - List allocatedResAll = new ArrayList(); + List allocatedResAll = new ArrayList<>(); - Map allocateToAllOrigin = new TreeMap(); + Map allocateToAllOrigin = new TreeMap<>(); //test allocate all { - List cidBegin = new ArrayList(cidAll); + List cidBegin = new ArrayList<>(cidAll); - //System.out.println("cidAll:" + cidBegin.toString()); for (String cid : cidBegin) { List rs = allocateMessageQueueConsistentHash.allocate("testConsumerGroup", cid, mqAll, cidBegin); for (MessageQueue mq : rs) { allocateToAllOrigin.put(mq, cid); } allocatedResAll.addAll(rs); - //System.out.println("rs[" + cid + "]:" + rs.toString()); } Assert.assertTrue( verifyAllocateAll(cidBegin, mqAll, allocatedResAll)); } - Map allocateToAllAfterRemoveOne = new TreeMap(); - List cidAfterRemoveOne = new ArrayList(cidAll); + Map allocateToAllAfterRemoveOne = new TreeMap<>(); + List cidAfterRemoveOne = new ArrayList<>(cidAll); //test allocate remove one cid { String removeCID = cidAfterRemoveOne.remove(0); - //System.out.println("removing one cid "+removeCID); - List mqShouldOnlyChanged = new ArrayList(); + List mqShouldOnlyChanged = new ArrayList<>(); Iterator> it = allocateToAllOrigin.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); @@ -146,15 +131,13 @@ public void testAllocate(int queueSize, int consumerSize) { } } - //System.out.println("cidAll:" + cidAfterRemoveOne.toString()); - List allocatedResAllAfterRemove = new ArrayList(); + 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); } - //System.out.println("rs[" + cid + "]:" + "[" + rs.size() + "]" + rs.toString()); } Assert.assertTrue("queueSize" + queueSize + "consumerSize:" + consumerSize + "\nmqAll:" + mqAll + "\nallocatedResAllAfterRemove" + allocatedResAllAfterRemove, @@ -162,16 +145,14 @@ public void testAllocate(int queueSize, int consumerSize) { verifyAfterRemove(allocateToAllOrigin, allocateToAllAfterRemoveOne, removeCID); } - List cidAfterAdd = new ArrayList(cidAfterRemoveOne); + List cidAfterAdd = new ArrayList<>(cidAfterRemoveOne); //test allocate add one more cid { String newCid = CID_PREFIX + "NEW"; - //System.out.println("add one more cid "+newCid); cidAfterAdd.add(newCid); - List mqShouldOnlyChanged = new ArrayList(); - //System.out.println("cidAll:" + cidAfterAdd.toString()); - List allocatedResAllAfterAdd = new ArrayList(); - Map allocateToAll3 = new TreeMap(); + 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); @@ -181,7 +162,6 @@ public void testAllocate(int queueSize, int consumerSize) { mqShouldOnlyChanged.add(mq); } } - //System.out.println("rs[" + cid + "]:" + "[" + rs.size() + "]" + rs.toString()); } Assert.assertTrue( @@ -204,7 +184,7 @@ private void verifyAfterRemove(Map allocateToBefore, Map allocateBefore, Map createConsumerIdList(int size) { - List consumerIdList = new ArrayList(size); + List consumerIdList = new ArrayList<>(size); for (int i = 0; i < size; i++) { consumerIdList.add(CID_PREFIX + String.valueOf(i)); } @@ -232,7 +212,7 @@ private List createConsumerIdList(int size) { } private List createMessageQueueList(int size) { - List messageQueueList = new ArrayList(size); + List messageQueueList = new ArrayList<>(size); for (int i = 0; i < size; i++) { MessageQueue mq = new MessageQueue(topic, "brokerName", i); messageQueueList.add(mq); diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/store/LocalFileOffsetStoreTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/store/LocalFileOffsetStoreTest.java index a705b30fc3b..c31c708dbb2 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/store/LocalFileOffsetStoreTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/store/LocalFileOffsetStoreTest.java @@ -71,7 +71,7 @@ public void testReadOffset_FromStore() throws Exception { offsetStore.updateOffset(messageQueue, 1024, false); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(-1); - offsetStore.persistAll(new HashSet(Collections.singletonList(messageQueue))); + offsetStore.persistAll(new HashSet<>(Collections.singletonList(messageQueue))); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1024); } diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/store/RemoteBrokerOffsetStoreTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/store/RemoteBrokerOffsetStoreTest.java index ec7a4cf0e27..33ea2b04b88 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/store/RemoteBrokerOffsetStoreTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/store/RemoteBrokerOffsetStoreTest.java @@ -20,14 +20,16 @@ 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.common.protocol.header.QueryConsumerOffsetRequestHeader; -import org.apache.rocketmq.common.protocol.header.UpdateConsumerOffsetRequestHeader; 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; @@ -61,6 +63,7 @@ public void init() { 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 @@ -85,10 +88,15 @@ public void testReadOffset_WithException() throws Exception { offsetStore.updateOffset(messageQueue, 1024, false); - doThrow(new MQBrokerException(-1, "", null)) + 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); @@ -121,7 +129,7 @@ public Object answer(InvocationOnMock mock) throws Throwable { assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1023); offsetStore.updateOffset(messageQueue, 1025, false); - offsetStore.persistAll(new HashSet(Collections.singletonList(messageQueue))); + offsetStore.persistAll(new HashSet<>(Collections.singletonList(messageQueue))); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE)).isEqualTo(1025); } @@ -136,4 +144,4 @@ public void testRemoveOffset() throws Exception { offsetStore.removeOffset(messageQueue); assertThat(offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_MEMORY)).isEqualTo(-1); } -} \ No newline at end of file +} diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java index c8446bdf104..d13f2cfe43a 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java @@ -16,7 +16,19 @@ */ package org.apache.rocketmq.client.impl; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CountDownLatch; 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.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.SendMessageContext; @@ -25,14 +37,17 @@ 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.AclConfig; +import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.PlainAccessConfig; +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.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader; -import org.apache.rocketmq.common.protocol.header.SendMessageResponseHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.AddWritePermOfBrokerResponseHeader; +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.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.exception.RemotingException; @@ -40,6 +55,33 @@ 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.QueryAssignmentResponseBody; +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.ExtraInfoUtil; +import org.apache.rocketmq.remoting.protocol.header.GetBrokerClusterAclConfigResponseBody; +import org.apache.rocketmq.remoting.protocol.header.GetBrokerClusterAclConfigResponseHeader; +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.QueryConsumerOffsetRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetResponseHeader; +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.namesrv.AddWritePermOfBrokerResponseHeader; +import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; +import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; +import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,8 +91,6 @@ import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; -import java.lang.reflect.Field; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; import static org.mockito.ArgumentMatchers.any; @@ -70,9 +110,11 @@ public class MQClientAPIImplTest { private String brokerAddr = "127.0.0.1"; private String brokerName = "DefaultBroker"; + private String clusterName = "DefaultCluster"; private static String group = "FooBarGroup"; private static String topic = "FooBar"; private Message msg = new Message("FooBar", new byte[] {}); + private static String clientId = "127.0.0.2@UnitTest"; @Before public void init() throws Exception { @@ -116,7 +158,7 @@ public void testSendMessageSync_Success() throws InterruptedException, RemotingE @Override public Object answer(InvocationOnMock mock) throws Throwable { RemotingCommand request = mock.getArgument(1); - return createSuccessResponse(request); + return createSendMessageSuccessResponse(request); } }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); @@ -169,7 +211,7 @@ public Object answer(InvocationOnMock mock) throws Throwable { InvokeCallback callback = mock.getArgument(3); RemotingCommand request = mock.getArgument(1); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); - responseFuture.setResponseCommand(createSuccessResponse(request)); + responseFuture.setResponseCommand(createSendMessageSuccessResponse(request)); callback.operationComplete(responseFuture); return null; } @@ -343,7 +385,7 @@ public Object answer(InvocationOnMock mock) throws Throwable { InvokeCallback callback = mock.getArgument(3); RemotingCommand request = mock.getArgument(1); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); - responseFuture.setResponseCommand(createSuccessResponse(request)); + responseFuture.setResponseCommand(createSendMessageSuccessResponse(request)); callback.operationComplete(responseFuture); return null; } @@ -367,6 +409,502 @@ public void onException(Throwable e) { }, null, null, 0, sendMessageContext, defaultMQProducerImpl); } + @Test + public void testQueryAssignment_Success() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock 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(new Answer() { + @Override + public Void answer(InvocationOnMock mock) throws Throwable { + 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, false, 0, 0L); + responseHeader.setStartOffsetInfo(startOffsetInfo.toString()); + StringBuilder msgOffsetInfo = new StringBuilder(64); + ExtraInfoUtil.buildMsgOffsetInfo(msgOffsetInfo, false, 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.operationComplete(responseFuture); + 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(new Answer() { + @Override + public Void answer(InvocationOnMock mock) throws Throwable { + 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, false, 0, 0L); + responseHeader.setStartOffsetInfo(startOffsetInfo.toString()); + StringBuilder msgOffsetInfo = new StringBuilder(64); + ExtraInfoUtil.buildMsgOffsetInfo(msgOffsetInfo, false, 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"); + message.putUserProperty(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.operationComplete(responseFuture); + 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 testAckMessageAsync_Success() throws Exception { + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock mock) throws Throwable { + 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.operationComplete(responseFuture); + 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(new Answer() { + @Override + public Void answer(InvocationOnMock mock) throws Throwable { + 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.operationComplete(responseFuture); + 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(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock 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(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock 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(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock 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 testGetBrokerClusterConfig() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + RemotingCommand response = RemotingCommand.createResponseCommand(GetBrokerClusterAclConfigResponseHeader.class); + GetBrokerClusterAclConfigResponseBody body = new GetBrokerClusterAclConfigResponseBody(); + body.setGlobalWhiteAddrs(Collections.singletonList("1.1.1.1")); + body.setPlainAccessConfigs(Collections.singletonList(new PlainAccessConfig())); + response.setBody(body.encode()); + response.makeCustomHeaderToNet(); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + AclConfig aclConfig = mqClientAPI.getBrokerClusterConfig(brokerAddr, 10000); + assertThat(aclConfig.getPlainAccessConfigs()).size().isGreaterThan(0); + assertThat(aclConfig.getGlobalWhiteAddrs()).size().isGreaterThan(0); + } + + @Test + public void testViewMessage() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) throws Exception { + 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, 100L, 10000); + assertThat(messageExt.getTopic()).isEqualTo(topic); + } + + @Test + public void testSearchOffset() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock 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(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock 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(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock 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(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock 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(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock 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(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock 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(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock 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); @@ -374,7 +912,7 @@ private RemotingCommand createResumeSuccessResponse(RemotingCommand request) { return response; } - private RemotingCommand createSuccessResponse(RemotingCommand request) { + private RemotingCommand createSendMessageSuccessResponse(RemotingCommand request) { RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); @@ -476,4 +1014,4 @@ public Object answer(InvocationOnMock invocationOnMock) throws Throwable { int topicCnt = mqClientAPI.addWritePermOfBroker("127.0.0.1", "default-broker", 1000); assertThat(topicCnt).isEqualTo(7); } -} \ No newline at end of file +} diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java index c12f2fc9e05..749201e3c22 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java @@ -27,7 +27,6 @@ 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; @@ -47,11 +46,12 @@ 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.protocol.header.PullMessageRequestHeader; 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.After; import org.junit.Before; import org.junit.Test; @@ -155,7 +155,7 @@ public PullResult answer(InvocationOnMock mock) throws Throwable { 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(); + Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createPullRequest().getMessageQueue()); pushConsumer.getDefaultMQPushConsumerImpl().updateTopicSubscribeInfo(topic, messageQueueSet); pushConsumer.start(); @@ -164,7 +164,7 @@ public PullResult answer(InvocationOnMock mock) throws Throwable { @Test public void testPullMessage_ConsumeSuccess() throws InterruptedException, RemotingException, MQBrokerException, NoSuchFieldException,Exception { final CountDownLatch countDownLatch = new CountDownLatch(1); - final AtomicReference messageAtomic = new AtomicReference(); + final AtomicReference messageAtomic = new AtomicReference<>(); ConsumeMessageConcurrentlyService normalServie = new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override @@ -183,7 +183,7 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, Thread.sleep(1000); - org.apache.rocketmq.common.protocol.body.ConsumeStatus stats = normalServie.getConsumerStatsManager().consumeStatus(pushConsumer.getDefaultMQPushConsumerImpl().groupName(),topic); + ConsumeStatus stats = normalServie.getConsumerStatsManager().consumeStatus(pushConsumer.getDefaultMQPushConsumerImpl().groupName(),topic); ConsumerStatsManager mgr = normalServie.getConsumerStatsManager(); @@ -235,7 +235,7 @@ private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, P @Test public void testConsumeThreadName() throws Exception { final CountDownLatch countDownLatch = new CountDownLatch(1); - final AtomicReference consumeThreadName = new AtomicReference(); + final AtomicReference consumeThreadName = new AtomicReference<>(); StringBuilder consumeGroup2 = new StringBuilder(); for (int i = 0; i < 101; i++) { @@ -256,7 +256,6 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); countDownLatch.await(); - System.out.println(consumeThreadName.get()); if (consumeGroup2.length() <= 100) { assertThat(consumeThreadName.get()).startsWith("ConsumeMessageThread_" + consumeGroup2 + "_"); } else { diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyServiceTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyServiceTest.java index 8ea1727a455..5fa78b70090 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyServiceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyServiceTest.java @@ -29,7 +29,6 @@ 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; @@ -47,9 +46,9 @@ 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.protocol.body.CMResult; -import org.apache.rocketmq.common.protocol.header.PullMessageRequestHeader; 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; @@ -152,7 +151,7 @@ public PullResult answer(InvocationOnMock mock) throws Throwable { 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(); + Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createPullRequest().getMessageQueue()); pushConsumer.getDefaultMQPushConsumerImpl().updateTopicSubscribeInfo(topic, messageQueueSet); pushConsumer.start(); @@ -202,7 +201,7 @@ public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderly @Test public void testConsumeThreadName() throws Exception { final CountDownLatch countDownLatch = new CountDownLatch(1); - final AtomicReference consumeThreadName = new AtomicReference(); + final AtomicReference consumeThreadName = new AtomicReference<>(); StringBuilder consumeGroup2 = new StringBuilder(); for (int i = 0; i < 101; i++) { @@ -225,7 +224,6 @@ public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderly PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); countDownLatch.await(); - System.out.println(consumeThreadName.get()); if (consumeGroup2.length() <= 100) { assertThat(consumeThreadName.get()).startsWith("ConsumeMessageThread_" + consumeGroup2 + "_"); } else { diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImplTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImplTest.java index d4f581231f0..879bbc593c1 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImplTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImplTest.java @@ -23,16 +23,31 @@ 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.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.common.message.MessageExt; +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 static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) public class DefaultMQPushConsumerImplTest { + @Mock + private DefaultMQPushConsumer defaultMQPushConsumer; @Rule public ExpectedException thrown = ExpectedException.none(); + @Test public void checkConfigTest() throws MQClientException { @@ -50,7 +65,6 @@ public void checkConfigTest() throws MQClientException { consumer.registerMessageListener(new MessageListenerConcurrently() { public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { - System.out.println(" Receive New Messages: " + msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); @@ -58,4 +72,58 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, DefaultMQPushConsumerImpl defaultMQPushConsumerImpl = new DefaultMQPushConsumerImpl(consumer, null); defaultMQPushConsumerImpl.start(); } + + @Test + public void testHook() throws Exception { + 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(new MessageListenerConcurrently() { + @Override + public ConsumeConcurrentlyStatus consumeMessage(List msgs, + ConsumeConcurrentlyContext 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(); + } + } } diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ProcessQueueTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ProcessQueueTest.java index 16e4a0d901b..be0bd29f79f 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ProcessQueueTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ProcessQueueTest.java @@ -20,7 +20,8 @@ import java.util.Collections; import java.util.List; import org.apache.rocketmq.common.message.MessageExt; -import org.apache.rocketmq.common.protocol.body.ProcessQueueInfo; +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; @@ -64,6 +65,18 @@ public void testCachedMessageSize() { 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() { ProcessQueue pq = new ProcessQueue(); @@ -95,7 +108,7 @@ private List createMessageList() { } private List createMessageList(int count) { - List messageExtList = new ArrayList(); + List messageExtList = new ArrayList<>(); for (int i = 0; i < count; i++) { MessageExt messageExt = new MessageExt(); messageExt.setQueueOffset(i); @@ -104,4 +117,4 @@ private List createMessageList(int count) { } return messageExtList; } -} \ No newline at end of file +} diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImplTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImplTest.java index ad244ebfceb..1dbda1c99a8 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImplTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImplTest.java @@ -53,9 +53,9 @@ public RebalanceLitePullImplTest() { @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}) { + 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); diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImplTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImplTest.java index a60d88ea019..f55b5869e56 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImplTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImplTest.java @@ -19,7 +19,6 @@ 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; @@ -30,22 +29,19 @@ import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; +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.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; 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.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -81,7 +77,7 @@ public void testMessageQueueChanged_CountThreshold() { // Just set pullThresholdForQueue defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdForQueue(1024); - Set allocateResultSet = new HashSet(); + Set allocateResultSet = new HashSet<>(); allocateResultSet.add(new MessageQueue(topic, "BrokerA", 0)); allocateResultSet.add(new MessageQueue(topic, "BrokerA", 1)); doRebalanceForcibly(rebalancePush, allocateResultSet); @@ -112,13 +108,6 @@ private void init(final RebalancePushImpl rebalancePush) { when(mqClientInstance.findConsumerIdList(anyString(), anyString())).thenReturn(Collections.singletonList(consumerGroup)); when(mqClientInstance.getClientId()).thenReturn(consumerGroup); when(defaultMQPushConsumer.getOffsetStore()).thenReturn(offsetStore); - - doAnswer(new Answer() { - @Override - public Object answer(final InvocationOnMock invocation) throws Throwable { - return null; - } - }).when(defaultMQPushConsumer).executePullRequestImmediately(any(PullRequest.class)); } @Test @@ -129,7 +118,7 @@ public void testMessageQueueChanged_SizeThreshold() { // Just set pullThresholdSizeForQueue defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdSizeForQueue(1024); - Set allocateResultSet = new HashSet(); + Set allocateResultSet = new HashSet<>(); allocateResultSet.add(new MessageQueue(topic, "BrokerA", 0)); allocateResultSet.add(new MessageQueue(topic, "BrokerA", 1)); doRebalanceForcibly(rebalancePush, allocateResultSet); @@ -154,7 +143,7 @@ public void testMessageQueueChanged_ConsumerRuntimeInfo() throws MQClientExcepti defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdSizeForQueue(1024); defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdForQueue(1024); - Set allocateResultSet = new HashSet(); + Set allocateResultSet = new HashSet<>(); allocateResultSet.add(new MessageQueue(topic, "BrokerA", 0)); allocateResultSet.add(new MessageQueue(topic, "BrokerA", 1)); doRebalanceForcibly(rebalancePush, allocateResultSet); @@ -185,16 +174,13 @@ public void testMessageQueueChanged_ConsumerRuntimeInfo() throws MQClientExcepti @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}) { + 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)); } } diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java index d37844ca963..acd792b8625 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java @@ -31,12 +31,12 @@ import org.apache.rocketmq.client.impl.consumer.MQConsumerInner; import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; -import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.exception.RemotingException; +import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo; +import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType; +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; @@ -51,7 +51,7 @@ public class MQClientInstanceTest { private MQClientInstance mqClientInstance = MQClientManager.getInstance().getOrCreateMQClientInstance(new ClientConfig()); private String topic = "FooBar"; private String group = "FooBarGroup"; - private ConcurrentMap> brokerAddrTable = new ConcurrentHashMap>(); + private ConcurrentMap> brokerAddrTable = new ConcurrentHashMap<>(); @Before public void init() throws Exception { @@ -62,18 +62,18 @@ public void init() throws Exception { public void testTopicRouteData2TopicPublishInfo() { TopicRouteData topicRouteData = new TopicRouteData(); - topicRouteData.setFilterServerTable(new HashMap>()); - List brokerDataList = new ArrayList(); + topicRouteData.setFilterServerTable(new HashMap<>()); + List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); - HashMap brokerAddrs = new HashMap(); + 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(); + List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); @@ -93,7 +93,7 @@ public void testTopicRouteData2TopicPublishInfo() { public void testFindBrokerAddressInSubscribe() { // dledger normal case String brokerName = "BrokerA"; - HashMap addrMap = new HashMap(); + 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"); @@ -106,7 +106,7 @@ public void testFindBrokerAddressInSubscribe() { // dledger case, when node n0 was voted as the leader brokerName = "BrokerB"; - HashMap addrMapNew = new HashMap(); + 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"); @@ -161,7 +161,7 @@ public void testConsumerRunningInfoWhenConsumersIsEmptyOrNot() throws RemotingEx runningInfo = mqClientInstance.consumerRunningInfo(group); assertThat(runningInfo).isNotNull(); - assertThat(mockConsumerInner.consumerRunningInfo().getProperties().get(ConsumerRunningInfo.PROP_CONSUME_TYPE)); + assertThat(mockConsumerInner.consumerRunningInfo().getProperties().get(ConsumerRunningInfo.PROP_CONSUME_TYPE)).isNotNull(); mqClientInstance.unregisterConsumer(group); flag = mqClientInstance.registerConsumer(group, mock(MQConsumerInner.class)); @@ -180,4 +180,5 @@ public void testRegisterAdminExt() { flag = mqClientInstance.registerAdminExt(group, mock(MQAdminExtInner.class)); assertThat(flag).isTrue(); } -} \ No newline at end of file + +} diff --git a/client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java b/client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java index b97b30e08fd..658f22ab0d8 100644 --- a/client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java @@ -42,13 +42,14 @@ import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; 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.protocol.header.SendMessageRequestHeader; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; 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; @@ -102,7 +103,7 @@ public void init() throws Exception { field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); - producer.getDefaultMQProducerImpl().getmQClientFactory().registerProducer(producerGroupTemp, producer.getDefaultMQProducerImpl()); + 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(); @@ -128,7 +129,7 @@ public void testSendMessage_ZeroMessage() throws InterruptedException, RemotingE @Test public void testSendMessage_NoNameSrv() throws RemotingException, InterruptedException, MQBrokerException { - when(mQClientAPIImpl.getNameServerAddressList()).thenReturn(new ArrayList()); + when(mQClientAPIImpl.getNameServerAddressList()).thenReturn(new ArrayList<>()); try { producer.send(message); failBecauseExceptionWasNotThrown(MQClientException.class); @@ -192,7 +193,7 @@ public void onException(Throwable e) { @Test public void testSendMessageAsync() throws RemotingException, MQClientException, InterruptedException { final AtomicInteger cc = new AtomicInteger(0); - final CountDownLatch countDownLatch = new CountDownLatch(6); + final CountDownLatch countDownLatch = new CountDownLatch(12); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); SendCallback sendCallback = new SendCallback() { @@ -215,6 +216,10 @@ public MessageQueue select(List mqs, Message msg, Object arg) { } }; + // 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()); @@ -228,8 +233,21 @@ public MessageQueue select(List mqs, Message msg, Object arg) { countDownLatch.await(3000L, 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(3000L, TimeUnit.MILLISECONDS); + assertThat(cc.get()).isEqualTo(10); } - + @Test public void testBatchSendMessageAsync() throws RemotingException, MQClientException, InterruptedException, MQBrokerException { @@ -257,13 +275,16 @@ public MessageQueue select(List mqs, Message msg, Object arg) { } }; - List msgs = new ArrayList(); + 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); @@ -273,6 +294,17 @@ public MessageQueue select(List mqs, Message msg, Object arg) { countDownLatch.await(3000L, 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(3000L, TimeUnit.MILLISECONDS); + assertThat(cc.get()).isEqualTo(2); } @Test @@ -358,7 +390,7 @@ public void testSetCallbackExecutor() throws MQClientException { producer.setCallbackExecutor(customized); NettyRemotingClient remotingClient = (NettyRemotingClient) producer.getDefaultMQProducerImpl() - .getmQClientFactory().getMQClientAPIImpl().getRemotingClient(); + .getMqClientFactory().getMQClientAPIImpl().getRemotingClient(); assertThat(remotingClient.getCallbackExecutor()).isEqualTo(customized); } @@ -368,7 +400,8 @@ public void testRequestMessage() throws RemotingException, RequestTimeoutExcepti when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); final AtomicBoolean finish = new AtomicBoolean(false); new Thread(new Runnable() { - @Override public void run() { + @Override + public void run() { ConcurrentHashMap responseMap = RequestFutureHolder.getInstance().getRequestFutureTable(); assertThat(responseMap).isNotNull(); while (!finish.get()) { @@ -376,15 +409,19 @@ public void testRequestMessage() throws RemotingException, RequestTimeoutExcepti 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(message); + 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'}); } @@ -400,24 +437,31 @@ 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) { + @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) { + @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); - message.setFlag(1); - future.getRequestCallback().onSuccess(message); + future.getRequestCallback().onSuccess(responseMsg); } countDownLatch.await(3000L, TimeUnit.MILLISECONDS); } @@ -427,11 +471,13 @@ 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 onSuccess(Message message) { } - @Override public void onException(Throwable e) { + @Override + public void onException(Throwable e) { cc.incrementAndGet(); countDownLatch.countDown(); } @@ -461,18 +507,18 @@ public MessageQueue select(List mqs, Message msg, Object arg) { public static TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); - topicRouteData.setFilterServerTable(new HashMap>()); - List brokerDataList = new ArrayList(); + topicRouteData.setFilterServerTable(new HashMap<>()); + List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); - HashMap brokerAddrs = new HashMap(); + 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(); + List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); diff --git a/client/src/test/java/org/apache/rocketmq/client/producer/RequestResponseFutureTest.java b/client/src/test/java/org/apache/rocketmq/client/producer/RequestResponseFutureTest.java index bc20873c114..68615f3b394 100644 --- a/client/src/test/java/org/apache/rocketmq/client/producer/RequestResponseFutureTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/producer/RequestResponseFutureTest.java @@ -30,11 +30,13 @@ public class RequestResponseFutureTest { 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) { + @Override + public void onSuccess(Message message) { cc.incrementAndGet(); } - @Override public void onException(Throwable e) { + @Override + public void onException(Throwable e) { } }); future.setSendRequestOk(true); diff --git a/client/src/test/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueByHashTest.java b/client/src/test/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueByHashTest.java index 8f286ee4295..8d5a24b3132 100644 --- a/client/src/test/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueByHashTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueByHashTest.java @@ -34,7 +34,7 @@ public void testSelect() throws Exception { Message message = new Message(topic, new byte[] {}); - List messageQueues = new ArrayList(); + List messageQueues = new ArrayList<>(); for (int i = 0; i < 10; i++) { MessageQueue messageQueue = new MessageQueue(topic, "DefaultBroker", i); messageQueues.add(messageQueue); diff --git a/client/src/test/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueRetryTest.java b/client/src/test/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueRetryTest.java index 36a57c9b065..df4dd87aca9 100644 --- a/client/src/test/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueRetryTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/producer/selector/SelectMessageQueueRetryTest.java @@ -17,7 +17,6 @@ package org.apache.rocketmq.client.producer.selector; import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; -import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.junit.Test; @@ -48,7 +47,7 @@ public void testSelect() throws Exception { topicPublishInfo.setMessageQueueList(messageQueueList); Set retryBrokerNameSet = retryBroker(topicPublishInfo); - //always in Set (broker-0,broker-1,broker-2) + //always in Set (broker-0, broker-1, broker-2) assertThat(retryBroker(topicPublishInfo)).isEqualTo(retryBrokerNameSet); } diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java index 4864522c66c..a39ae4a4ded 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java @@ -62,9 +62,9 @@ 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.protocol.header.PullMessageRequestHeader; 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; @@ -160,7 +160,7 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, doReturn(new FindBrokerResult("127.0.0.1:10911", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); - Set messageQueueSet = new HashSet(); + Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createPullRequest().getMessageQueue()); pushConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); @@ -176,7 +176,7 @@ public void terminate() { @Test public void testPullMessage_WithTrace_Success() throws InterruptedException, RemotingException, MQBrokerException, MQClientException { final CountDownLatch countDownLatch = new CountDownLatch(1); - final AtomicReference messageAtomic = new AtomicReference(); + final AtomicReference messageAtomic = new AtomicReference<>(); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, @@ -197,7 +197,8 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, // wait until consumeMessageAfter hook of tracer is done surely. waitAtMost(1, TimeUnit.SECONDS).until(new Callable() { - @Override public Object call() throws Exception { + @Override + public Object call() throws Exception { return tracer.finishedSpans().size() == 1; } }); diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithTraceTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithTraceTest.java index f7771ec597b..6283abd6b2f 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithTraceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithTraceTest.java @@ -62,13 +62,13 @@ 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.protocol.header.PullMessageRequestHeader; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; 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; @@ -89,7 +89,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) +@RunWith(MockitoJUnitRunner.Silent.class) public class DefaultMQConsumerWithTraceTest { private String consumerGroup; private String consumerGroupNormal; @@ -105,7 +105,7 @@ public class DefaultMQConsumerWithTraceTest { private RebalancePushImpl rebalancePushImpl; private DefaultMQPushConsumer pushConsumer; private DefaultMQPushConsumer normalPushConsumer; - private DefaultMQPushConsumer customTraceTopicpushConsumer; + private DefaultMQPushConsumer customTraceTopicPushConsumer; private AsyncTraceDispatcher asyncTraceDispatcher; private MQClientInstance mQClientTraceFactory; @@ -126,7 +126,7 @@ public void init() throws Exception { pushConsumer = new DefaultMQPushConsumer(consumerGroup, true, ""); consumerGroupNormal = "FooBarGroup" + System.currentTimeMillis(); normalPushConsumer = new DefaultMQPushConsumer(consumerGroupNormal, false, ""); - customTraceTopicpushConsumer = new DefaultMQPushConsumer(consumerGroup, true, customerTraceTopic); + customTraceTopicPushConsumer = new DefaultMQPushConsumer(consumerGroup, true, customerTraceTopic); pushConsumer.setNamesrvAddr("127.0.0.1:9876"); pushConsumer.setPullInterval(60 * 1000); @@ -205,7 +205,7 @@ public PullResult answer(InvocationOnMock mock) throws Throwable { }); doReturn(new FindBrokerResult("127.0.0.1:10911", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); - Set messageQueueSet = new HashSet(); + Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createPullRequest().getMessageQueue()); pushConsumer.getDefaultMQPushConsumerImpl().updateTopicSubscribeInfo(topic, messageQueueSet); } @@ -217,10 +217,10 @@ public void terminate() { @Test public void testPullMessage_WithTrace_Success() throws InterruptedException, RemotingException, MQBrokerException, MQClientException { - traceProducer.getDefaultMQProducerImpl().getmQClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); + traceProducer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); final CountDownLatch countDownLatch = new CountDownLatch(1); - final AtomicReference messageAtomic = new AtomicReference(); + final AtomicReference messageAtomic = new AtomicReference<>(); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, @@ -239,7 +239,7 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, assertThat(msg.getTopic()).isEqualTo(topic); assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); } - + @Test public void testPushConsumerWithTraceTLS() { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumerGroup", true); @@ -278,18 +278,18 @@ private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, P public static TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); - topicRouteData.setFilterServerTable(new HashMap>()); - List brokerDataList = new ArrayList(); + topicRouteData.setFilterServerTable(new HashMap<>()); + List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); - HashMap brokerAddrs = new HashMap(); + 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(); + List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); @@ -314,18 +314,18 @@ private SendResult createSendResult(SendStatus sendStatus) { public static TopicRouteData createTraceTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); - topicRouteData.setFilterServerTable(new HashMap>()); - List brokerDataList = new ArrayList(); + topicRouteData.setFilterServerTable(new HashMap<>()); + List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("broker-trace"); brokerData.setCluster("DefaultCluster"); - HashMap brokerAddrs = new HashMap(); + 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(); + List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("broker-trace"); queueData.setPerm(6); diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java index 3f7031e8fe6..e0573bdfb0b 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java @@ -16,6 +16,15 @@ */ 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.ClientConfig; @@ -44,15 +53,16 @@ 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.protocol.header.PullMessageRequestHeader; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; 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.Assert; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -61,16 +71,6 @@ 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.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -104,6 +104,11 @@ public class DefaultMQLitePullConsumerWithTraceTest { 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 { Field field = MQClientInstance.class.getDeclaredField("rebalanceService"); @@ -118,7 +123,7 @@ public void init() throws Exception { public void testSubscribe_PollMessageSuccess_WithDefaultTraceTopic() throws Exception { DefaultLitePullConsumer litePullConsumer = createLitePullConsumerWithDefaultTraceTopic(); try { - Set messageQueueSet = new HashSet(); + Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createMessageQueue()); litePullConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); litePullConsumer.setPollTimeoutMillis(20 * 1000); @@ -134,7 +139,7 @@ public void testSubscribe_PollMessageSuccess_WithDefaultTraceTopic() throws Exce public void testSubscribe_PollMessageSuccess_WithCustomizedTraceTopic() throws Exception { DefaultLitePullConsumer litePullConsumer = createLitePullConsumerWithCustomizedTraceTopic(); try { - Set messageQueueSet = new HashSet(); + Set messageQueueSet = new HashSet<>(); messageQueueSet.add(createMessageQueue()); litePullConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); litePullConsumer.setPollTimeoutMillis(20 * 1000); @@ -218,7 +223,7 @@ private void initDefaultLitePullConsumer(DefaultLitePullConsumer litePullConsume field.setAccessible(true); field.set(litePullConsumerImpl, offsetStore); - traceProducer.getDefaultMQProducerImpl().getmQClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); + traceProducer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); when(mQClientFactory.getMQClientAPIImpl().pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) @@ -267,18 +272,18 @@ private MessageQueue createMessageQueue() { private TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); - topicRouteData.setFilterServerTable(new HashMap>()); - List brokerDataList = new ArrayList(); + topicRouteData.setFilterServerTable(new HashMap<>()); + List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); - HashMap brokerAddrs = new HashMap(); + 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(); + List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQProducerWithOpenTracingTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQProducerWithOpenTracingTest.java index 5d64a93f039..8fbc70ea44f 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQProducerWithOpenTracingTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQProducerWithOpenTracingTest.java @@ -20,6 +20,10 @@ 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; @@ -37,12 +41,12 @@ 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.protocol.header.SendMessageRequestHeader; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; 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; @@ -51,11 +55,6 @@ import org.mockito.Spy; 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.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -100,7 +99,7 @@ public void init() throws Exception { field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); - producer.getDefaultMQProducerImpl().getmQClientFactory().registerProducer(producerGroupTemp, producer.getDefaultMQProducerImpl()); + 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(); @@ -112,7 +111,7 @@ public void init() throws Exception { @Test public void testSendMessageSync_WithTrace_Success() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { - producer.getDefaultMQProducerImpl().getmQClientFactory().registerProducer(producerGroupTraceTemp, producer.getDefaultMQProducerImpl()); + producer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTraceTemp, producer.getDefaultMQProducerImpl()); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); producer.send(message); assertThat(tracer.finishedSpans().size()).isEqualTo(1); @@ -134,18 +133,18 @@ public void terminate() { public static TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); - topicRouteData.setFilterServerTable(new HashMap>()); - List brokerDataList = new ArrayList(); + topicRouteData.setFilterServerTable(new HashMap<>()); + List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); - HashMap brokerAddrs = new HashMap(); + 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(); + List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQProducerWithTraceTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQProducerWithTraceTest.java index 234e32e6807..ff1fdfc544b 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQProducerWithTraceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQProducerWithTraceTest.java @@ -17,6 +17,12 @@ 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; @@ -32,12 +38,12 @@ 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.protocol.header.SendMessageRequestHeader; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; 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; @@ -47,14 +53,11 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; -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 static org.mockito.ArgumentMatchers.*; +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) @@ -109,7 +112,7 @@ public void init() throws Exception { field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); - producer.getDefaultMQProducerImpl().getmQClientFactory().registerProducer(producerGroupTemp, producer.getDefaultMQProducerImpl()); + 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(); @@ -121,7 +124,7 @@ public void init() throws Exception { @Test public void testSendMessageSync_WithTrace_Success() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { - traceProducer.getDefaultMQProducerImpl().getmQClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); + traceProducer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); final CountDownLatch countDownLatch = new CountDownLatch(1); try { @@ -144,7 +147,7 @@ public void testSendMessageSync_WithTrace_NoBrokerSet_Exception() throws Remotin } - + @Test public void testProducerWithTraceTLS() { DefaultMQProducer producer = new DefaultMQProducer(producerGroupTemp, true); @@ -152,7 +155,7 @@ public void testProducerWithTraceTLS() { AsyncTraceDispatcher asyncTraceDispatcher = (AsyncTraceDispatcher) producer.getTraceDispatcher(); Assert.assertTrue(asyncTraceDispatcher.getTraceProducer().isUseTLS()); } - + @After public void terminate() { producer.shutdown(); @@ -161,18 +164,18 @@ public void terminate() { public static TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); - topicRouteData.setFilterServerTable(new HashMap>()); - List brokerDataList = new ArrayList(); + topicRouteData.setFilterServerTable(new HashMap<>()); + List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); - HashMap brokerAddrs = new HashMap(); + 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(); + List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); @@ -197,18 +200,18 @@ private SendResult createSendResult(SendStatus sendStatus) { public static TopicRouteData createTraceTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); - topicRouteData.setFilterServerTable(new HashMap>()); - List brokerDataList = new ArrayList(); + topicRouteData.setFilterServerTable(new HashMap<>()); + List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("broker-trace"); brokerData.setCluster("DefaultCluster"); - HashMap brokerAddrs = new HashMap(); + 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(); + List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("broker-trace"); queueData.setPerm(6); diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/TraceDataEncoderTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/TraceDataEncoderTest.java index fed8c4ef767..763de9f3b3a 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/TraceDataEncoderTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/TraceDataEncoderTest.java @@ -82,7 +82,7 @@ public void testEncoderFromContextBean() { traceBean.setStoreTime(time); traceBean.setMsgType(MessageType.Normal_Msg); traceBean.setBodyLength(26); - List traceBeans = new ArrayList(); + List traceBeans = new ArrayList<>(); traceBeans.add(traceBean); context.setTraceBeans(traceBeans); TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(context); @@ -108,7 +108,7 @@ public void testEncoderFromContextBean_EndTransaction() { traceBean.setTransactionId("transactionId"); traceBean.setTransactionState(LocalTransactionState.COMMIT_MESSAGE); traceBean.setFromTransactionCheck(false); - List traceBeans = new ArrayList(); + List traceBeans = new ArrayList<>(); traceBeans.add(traceBean); context.setTraceBeans(traceBeans); TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(context); @@ -151,7 +151,7 @@ public void testPubTraceDataFormatTest() { bean.setBodyLength(100); bean.setMsgType(MessageType.Normal_Msg); bean.setOffsetMsgId("AC1415116D1418B4AAC217FE1B4E0000"); - pubContext.setTraceBeans(new ArrayList(1)); + pubContext.setTraceBeans(new ArrayList<>(1)); pubContext.getTraceBeans().add(bean); TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(pubContext); @@ -174,7 +174,7 @@ public void testSubBeforeTraceDataFormatTest() { bean.setMsgId("AC1415116D1418B4AAC217FE1B4E0000"); bean.setRetryTimes(0); bean.setKeys("keys"); - subBeforeContext.setTraceBeans(new ArrayList(1)); + subBeforeContext.setTraceBeans(new ArrayList<>(1)); subBeforeContext.getTraceBeans().add(bean); TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(subBeforeContext); @@ -198,7 +198,7 @@ public void testSubAfterTraceDataFormatTest() { TraceBean bean = new TraceBean(); bean.setMsgId("AC1415116D1418B4AAC217FE1B4E0000"); bean.setKeys("keys"); - subAfterContext.setTraceBeans(new ArrayList(1)); + subAfterContext.setTraceBeans(new ArrayList<>(1)); subAfterContext.getTraceBeans().add(bean); TraceTransferBean traceTransferBean = TraceDataEncoder.encoderFromContextBean(subAfterContext); @@ -226,7 +226,7 @@ public void testEndTrxTraceDataFormatTest() { endTrxTraceBean.setTransactionId("transactionId"); endTrxTraceBean.setTransactionState(LocalTransactionState.COMMIT_MESSAGE); endTrxTraceBean.setFromTransactionCheck(false); - List traceBeans = new ArrayList(); + List traceBeans = new ArrayList<>(); traceBeans.add(endTrxTraceBean); endTrxContext.setTraceBeans(traceBeans); @@ -255,7 +255,7 @@ public void testTraceKeys() { endTrxTraceBean.setTransactionId("transactionId"); endTrxTraceBean.setTransactionState(LocalTransactionState.COMMIT_MESSAGE); endTrxTraceBean.setFromTransactionCheck(false); - List traceBeans = new ArrayList(); + List traceBeans = new ArrayList<>(); traceBeans.add(endTrxTraceBean); endTrxContext.setTraceBeans(traceBeans); diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/TransactionMQProducerWithOpenTracingTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/TransactionMQProducerWithOpenTracingTest.java index dd6d1083ce0..5646a17dbe6 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/TransactionMQProducerWithOpenTracingTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/TransactionMQProducerWithOpenTracingTest.java @@ -20,6 +20,11 @@ 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; @@ -43,12 +48,12 @@ 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.protocol.header.SendMessageRequestHeader; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; 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; @@ -57,12 +62,6 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; -import java.lang.reflect.Field; -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -118,7 +117,7 @@ public LocalTransactionState checkLocalTransaction(MessageExt msg) { field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); - producer.getDefaultMQProducerImpl().getmQClientFactory().registerProducer(producerGroupTemp, producer.getDefaultMQProducerImpl()); + 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(); @@ -130,7 +129,7 @@ public LocalTransactionState checkLocalTransaction(MessageExt msg) { @Test public void testSendMessageSync_WithTrace_Success() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { - producer.getDefaultMQProducerImpl().getmQClientFactory().registerProducer(producerGroupTraceTemp, producer.getDefaultMQProducerImpl()); + producer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTraceTemp, producer.getDefaultMQProducerImpl()); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); producer.sendMessageInTransaction(message, null); @@ -152,18 +151,18 @@ public void terminate() { public static TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); - topicRouteData.setFilterServerTable(new HashMap>()); - List brokerDataList = new ArrayList(); + topicRouteData.setFilterServerTable(new HashMap<>()); + List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); - HashMap brokerAddrs = new HashMap(); + 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(); + List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/TransactionMQProducerWithTraceTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/TransactionMQProducerWithTraceTest.java index a2905017557..55d073a1abe 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/TransactionMQProducerWithTraceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/TransactionMQProducerWithTraceTest.java @@ -46,12 +46,12 @@ 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.protocol.header.SendMessageRequestHeader; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; 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; @@ -128,11 +128,11 @@ public LocalTransactionState checkLocalTransaction(MessageExt msg) { field.setAccessible(true); field.set(mQClientFactory, mQClientAPIImpl); - producer.getDefaultMQProducerImpl().getmQClientFactory().registerProducer(producerGroupTemp, producer.getDefaultMQProducerImpl()); + producer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTemp, producer.getDefaultMQProducerImpl()); Field fieldHooks = DefaultMQProducerImpl.class.getDeclaredField("endTransactionHookList"); fieldHooks.setAccessible(true); - List hooks = new ArrayList(); + List hooks = new ArrayList<>(); hooks.add(endTransactionHook); fieldHooks.set(producer.getDefaultMQProducerImpl(), hooks); @@ -146,11 +146,12 @@ public LocalTransactionState checkLocalTransaction(MessageExt msg) { @Test public void testSendMessageSync_WithTrace_Success() throws RemotingException, InterruptedException, MQBrokerException, MQClientException { - traceProducer.getDefaultMQProducerImpl().getmQClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); + traceProducer.getDefaultMQProducerImpl().getMqClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); when(mQClientAPIImpl.getTopicRouteInfoFromNameServer(anyString(), anyLong())).thenReturn(createTopicRoute()); - final AtomicReference context = new AtomicReference(); + final AtomicReference context = new AtomicReference<>(); doAnswer(new Answer() { - @Override public Object answer(InvocationOnMock mock) throws Throwable { + @Override + public Object answer(InvocationOnMock mock) throws Throwable { context.set((EndTransactionContext) mock.getArgument(0)); return null; } @@ -174,18 +175,18 @@ public void terminate() { public static TopicRouteData createTopicRoute() { TopicRouteData topicRouteData = new TopicRouteData(); - topicRouteData.setFilterServerTable(new HashMap>()); - List brokerDataList = new ArrayList(); + topicRouteData.setFilterServerTable(new HashMap<>()); + List brokerDataList = new ArrayList<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("BrokerA"); brokerData.setCluster("DefaultCluster"); - HashMap brokerAddrs = new HashMap(); + 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(); + List queueDataList = new ArrayList<>(); QueueData queueData = new QueueData(); queueData.setBrokerName("BrokerA"); queueData.setPerm(6); diff --git a/client/src/test/resources/org/powermock/extensions/configuration.properties b/client/src/test/resources/org/powermock/extensions/configuration.properties new file mode 100644 index 00000000000..6389eff4af9 --- /dev/null +++ b/client/src/test/resources/org/powermock/extensions/configuration.properties @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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.* \ No newline at end of file diff --git a/broker/src/test/resources/logback-test.xml b/client/src/test/resources/rmq.logback-test.xml similarity index 64% rename from broker/src/test/resources/logback-test.xml rename to client/src/test/resources/rmq.logback-test.xml index 1978b73ae07..8695d52d57c 100644 --- a/broker/src/test/resources/logback-test.xml +++ b/client/src/test/resources/rmq.logback-test.xml @@ -15,18 +15,22 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - %d{yyy-MM-dd HH\:mm\:ss,GMT+8} %p %t - %m%n - UTF-8 - + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + - - - + + + + + + - + + \ No newline at end of file diff --git a/common/BUILD.bazel b/common/BUILD.bazel new file mode 100644 index 00000000000..831c85e3d8c --- /dev/null +++ b/common/BUILD.bazel @@ -0,0 +1,73 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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_fastjson", + "@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//:org_apache_commons_commons_lang3", + "@maven//:org_lz4_lz4_java", + "@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 = [ + ":common", + "//:test_deps", + "@maven//:com_google_guava_guava", + "@maven//:com_alibaba_fastjson", + "@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_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", + ], +) diff --git a/common/pom.xml b/common/pom.xml index 610be62347a..01a4390891f 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.4-SNAPSHOT + 5.1.3 4.0.0 @@ -27,10 +27,18 @@ rocketmq-common rocketmq-common ${project.version} + + ${basedir}/.. + + - ${project.groupId} - rocketmq-remoting + com.alibaba + fastjson + + + io.netty + netty-all org.apache.commons @@ -40,5 +48,61 @@ commons-validator commons-validator + + com.github.luben + zstd-jni + + + org.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.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 + diff --git a/common/src/main/java/org/apache/rocketmq/common/AbortProcessException.java b/common/src/main/java/org/apache/rocketmq/common/AbortProcessException.java new file mode 100644 index 00000000000..562fdaac63c --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/AbortProcessException.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} \ No newline at end of file diff --git a/broker/src/main/java/org/apache/rocketmq/broker/filtersrv/FilterServerUtil.java b/common/src/main/java/org/apache/rocketmq/common/AbstractBrokerRunnable.java similarity index 53% rename from broker/src/main/java/org/apache/rocketmq/broker/filtersrv/FilterServerUtil.java rename to common/src/main/java/org/apache/rocketmq/common/AbstractBrokerRunnable.java index 3f4d24d0624..34aabc5772c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/filtersrv/FilterServerUtil.java +++ b/common/src/main/java/org/apache/rocketmq/common/AbstractBrokerRunnable.java @@ -15,28 +15,34 @@ * limitations under the License. */ -package org.apache.rocketmq.broker.filtersrv; +package org.apache.rocketmq.common; +import java.io.File; +import org.apache.rocketmq.logging.org.slf4j.MDC; -import org.apache.rocketmq.logging.InternalLogger; +public abstract class AbstractBrokerRunnable implements Runnable { + protected final BrokerIdentity brokerIdentity; -public class FilterServerUtil { - public static void callShell(final String shellString, final InternalLogger log) { - Process process = null; + public AbstractBrokerRunnable(BrokerIdentity brokerIdentity) { + this.brokerIdentity = brokerIdentity; + } + + private static final String MDC_BROKER_CONTAINER_LOG_DIR = "brokerContainerLogDir"; + + /** + * real logic for running + */ + public abstract void run0(); + + @Override + public void run() { try { - String[] cmdArray = splitShellString(shellString); - process = Runtime.getRuntime().exec(cmdArray); - process.waitFor(); - log.info("CallShell: <{}> OK", shellString); - } catch (Throwable e) { - log.error("CallShell: readLine IOException, {}", shellString, e); + if (brokerIdentity.isInBrokerContainer()) { + MDC.put(MDC_BROKER_CONTAINER_LOG_DIR, File.separator + brokerIdentity.getCanonicalName()); + } + run0(); } finally { - if (null != process) - process.destroy(); + MDC.clear(); } } - - private static String[] splitShellString(final String shellString) { - return shellString.split(" "); - } } diff --git a/common/src/main/java/org/apache/rocketmq/common/AclConfig.java b/common/src/main/java/org/apache/rocketmq/common/AclConfig.java index 191236a0998..49b9e05e2e1 100644 --- a/common/src/main/java/org/apache/rocketmq/common/AclConfig.java +++ b/common/src/main/java/org/apache/rocketmq/common/AclConfig.java @@ -40,4 +40,12 @@ public List getPlainAccessConfigs() { public void setPlainAccessConfigs(List plainAccessConfigs) { this.plainAccessConfigs = plainAccessConfigs; } + + @Override + public String toString() { + return "AclConfig{" + + "globalWhiteAddrs=" + globalWhiteAddrs + + ", plainAccessConfigs=" + plainAccessConfigs + + '}'; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/BoundaryType.java b/common/src/main/java/org/apache/rocketmq/common/BoundaryType.java new file mode 100644 index 00000000000..03a01710fa4 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/BoundaryType.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java index 401b4578815..f5f0db10166 100644 --- a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java +++ b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java @@ -16,31 +16,36 @@ */ package org.apache.rocketmq.common; -import java.net.InetAddress; -import java.net.UnknownHostException; import org.apache.rocketmq.common.annotation.ImportantField; -import org.apache.rocketmq.common.constant.LoggerName; 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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.remoting.common.RemotingUtil; +import org.apache.rocketmq.common.utils.NetworkUtil; -public class BrokerConfig { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); +import java.util.concurrent.TimeUnit; + +public class BrokerConfig extends BrokerIdentity { + + private String brokerConfigPath = null; private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV)); @ImportantField private String namesrvAddr = System.getProperty(MixAll.NAMESRV_ADDR_PROPERTY, System.getenv(MixAll.NAMESRV_ADDR_ENV)); + + /** + * Listen port for single broker + */ @ImportantField - private String brokerIP1 = RemotingUtil.getLocalAddress(); - private String brokerIP2 = RemotingUtil.getLocalAddress(); - @ImportantField - private String brokerName = localHostName(); + private int listenPort = 6888; + @ImportantField - private String brokerClusterName = "DefaultCluster"; + private String brokerIP1 = NetworkUtil.getLocalAddress(); + private String brokerIP2 = NetworkUtil.getLocalAddress(); + @ImportantField - private long brokerId = MixAll.MASTER_ID; + private boolean recoverConcurrently = false; + private int brokerPermission = PermName.PERM_READ | PermName.PERM_WRITE; private int defaultTopicQueueNums = 8; @ImportantField @@ -52,6 +57,8 @@ public class BrokerConfig { @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 @@ -59,21 +66,25 @@ public class BrokerConfig { /** * thread numbers for send message thread pool. */ - private int sendMessageThreadPoolNums = Math.min(Runtime.getRuntime().availableProcessors(), 4); - private int putMessageFutureThreadPoolNums = Math.min(Runtime.getRuntime().availableProcessors(), 4); - private int pullMessageThreadPoolNums = 16 + Runtime.getRuntime().availableProcessors() * 2; - private int processReplyMessageThreadPoolNums = 16 + Runtime.getRuntime().availableProcessors() * 2; - private int queryMessageThreadPoolNums = 8 + Runtime.getRuntime().availableProcessors(); + 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 = 3; + 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 heartbeatThreadPoolNums = Math.min(32, Runtime.getRuntime().availableProcessors()); + 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 + Runtime.getRuntime().availableProcessors() * 2, + private int endTransactionThreadPoolNums = Math.max(8 + PROCESSOR_NUMBER * 2, sendMessageThreadPoolNums * 4); private int flushConsumerOffsetInterval = 1000 * 5; @@ -82,19 +93,26 @@ public class BrokerConfig { @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 filterServerNums = 0; + private int adminBrokerThreadPoolQueueCapacity = 10000; + private int loadBalanceThreadPoolQueueCapacity = 100000; private boolean longPollingEnable = true; @@ -104,17 +122,19 @@ public class BrokerConfig { private boolean highSpeedMode = false; - private boolean commercialEnable = true; - private int commercialTimerCount = 1; - private int commercialTransCount = 1; - private int commercialBigCount = 1; private int commercialBaseCount = 1; + private int commercialSizePerMsg = 4 * 1024; + + private boolean accountStatsEnable = true; + private boolean accountStatsPrintZeroValues = true; + private boolean transferMsgByHeap = true; - private int maxDelayTime = 40; private String regionId = MixAll.DEFAULT_TRACE_REGION_ID; - private int registerBrokerTimeoutMills = 6000; + private int registerBrokerTimeoutMills = 24000; + + private int sendHeartbeatTimeoutMillis = 1000; private boolean slaveReadEnable = false; @@ -124,8 +144,10 @@ public class BrokerConfig { 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 startAcceptSendRequestTimeStamp = 0L; @@ -137,6 +159,9 @@ public class BrokerConfig { // 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; @@ -156,10 +181,71 @@ public class BrokerConfig { /** * This configurable item defines interval of topics registration of broker to name server. Allowing values are - * between 10, 000 and 60, 000 milliseconds. + * between 10,000 and 60,000 milliseconds. */ private int registerNameServerPeriod = 1000 * 30; + /** + * 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; + // 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; + private boolean enableNotifyAfterPopOrderLockRelease = true; + + private boolean realTimeNotifyConsumerChange = true; + + 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. @@ -177,7 +263,14 @@ public class BrokerConfig { * Transaction message check interval. */ @ImportantField - private long transactionCheckInterval = 60 * 1000; + private long transactionCheckInterval = 30 * 1000; + + /** + * transaction batch op message + */ + private int transactionOpMsgMaxSize = 4096; + + private int transactionOpBatchInterval = 3000; /** * Acl feature switch @@ -196,14 +289,224 @@ public class BrokerConfig { */ private boolean isolateLogEnable = false; - public static String localHostName() { - try { - return InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - log.error("Failed to obtain the host name", e); - } + 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 = false; + + 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 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; + 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; + + 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 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; + } - return "DEFAULT_BROKER"; + 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 boolean isTraceOn() { @@ -310,22 +613,6 @@ public void setHighSpeedMode(final boolean highSpeedMode) { this.highSpeedMode = highSpeedMode; } - public String getRocketmqHome() { - return rocketmqHome; - } - - public void setRocketmqHome(String rocketmqHome) { - this.rocketmqHome = rocketmqHome; - } - - public String getBrokerName() { - return brokerName; - } - - public void setBrokerName(String brokerName) { - this.brokerName = brokerName; - } - public int getBrokerPermission() { return brokerPermission; } @@ -350,14 +637,6 @@ public void setAutoCreateTopicEnable(boolean autoCreateTopic) { this.autoCreateTopicEnable = autoCreateTopic; } - public String getBrokerClusterName() { - return brokerClusterName; - } - - public void setBrokerClusterName(String brokerClusterName) { - this.brokerClusterName = brokerClusterName; - } - public String getBrokerIP1() { return brokerIP1; } @@ -398,6 +677,14 @@ 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; } @@ -454,14 +741,6 @@ public void setNamesrvAddr(String namesrvAddr) { this.namesrvAddr = namesrvAddr; } - public long getBrokerId() { - return brokerId; - } - - public void setBrokerId(long brokerId) { - this.brokerId = brokerId; - } - public boolean isAutoCreateSubscriptionGroup() { return autoCreateSubscriptionGroup; } @@ -470,6 +749,94 @@ 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; } @@ -510,6 +877,14 @@ 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; } @@ -534,14 +909,6 @@ public void setBrokerTopicEnable(boolean brokerTopicEnable) { this.brokerTopicEnable = brokerTopicEnable; } - public int getFilterServerNums() { - return filterServerNums; - } - - public void setFilterServerNums(int filterServerNums) { - this.filterServerNums = filterServerNums; - } - public boolean isLongPollingEnable() { return longPollingEnable; } @@ -574,46 +941,6 @@ public void setClientManageThreadPoolNums(int clientManageThreadPoolNums) { this.clientManageThreadPoolNums = clientManageThreadPoolNums; } - public boolean isCommercialEnable() { - return commercialEnable; - } - - public void setCommercialEnable(final boolean commercialEnable) { - this.commercialEnable = commercialEnable; - } - - public int getCommercialTimerCount() { - return commercialTimerCount; - } - - public void setCommercialTimerCount(final int commercialTimerCount) { - this.commercialTimerCount = commercialTimerCount; - } - - public int getCommercialTransCount() { - return commercialTransCount; - } - - public void setCommercialTransCount(final int commercialTransCount) { - this.commercialTransCount = commercialTransCount; - } - - public int getCommercialBigCount() { - return commercialBigCount; - } - - public void setCommercialBigCount(final int commercialBigCount) { - this.commercialBigCount = commercialBigCount; - } - - public int getMaxDelayTime() { - return maxDelayTime; - } - - public void setMaxDelayTime(final int maxDelayTime) { - this.maxDelayTime = maxDelayTime; - } - public int getClientManagerThreadPoolQueueCapacity() { return clientManagerThreadPoolQueueCapacity; } @@ -838,6 +1165,198 @@ 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 boolean isEnableNotifyAfterPopOrderLockRelease() { + return enableNotifyAfterPopOrderLockRelease; + } + + public void setEnableNotifyAfterPopOrderLockRelease(boolean enableNotifyAfterPopOrderLockRelease) { + this.enableNotifyAfterPopOrderLockRelease = enableNotifyAfterPopOrderLockRelease; + } + + 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 boolean isLockInStrictMode() { + return lockInStrictMode; + } + + public void setLockInStrictMode(boolean lockInStrictMode) { + this.lockInStrictMode = lockInStrictMode; + } + public boolean isIsolateLogEnable() { return isolateLogEnable; } @@ -845,4 +1364,316 @@ public boolean isIsolateLogEnable() { 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 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 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; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/BrokerIdentity.java b/common/src/main/java/org/apache/rocketmq/common/BrokerIdentity.java new file mode 100644 index 00000000000..e85a3aac728 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/BrokerIdentity.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/ConfigManager.java b/common/src/main/java/org/apache/rocketmq/common/ConfigManager.java index 99b5f0c667b..f712e1694d8 100644 --- a/common/src/main/java/org/apache/rocketmq/common/ConfigManager.java +++ b/common/src/main/java/org/apache/rocketmq/common/ConfigManager.java @@ -17,12 +17,13 @@ package org.apache.rocketmq.common; import java.io.IOException; +import java.util.Map; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public abstract class ConfigManager { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); public abstract String encode(); @@ -67,6 +68,16 @@ private boolean loadBak() { public abstract void decode(final String jsonString); + 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) { diff --git a/common/src/main/java/org/apache/rocketmq/common/ControllerConfig.java b/common/src/main/java/org/apache/rocketmq/common/ControllerConfig.java new file mode 100644 index 00000000000..1e9c80b2220 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/ControllerConfig.java @@ -0,0 +1,283 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV)); + private String configStorePath = System.getProperty("user.home") + File.separator + "controller" + File.separator + "controller.properties"; + + /** + * 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 = System.getProperty("user.home") + File.separator + "DledgerController"; + + /** + * 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; + + 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() { + 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 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/CountDownLatch2.java b/common/src/main/java/org/apache/rocketmq/common/CountDownLatch2.java index 9c95fff7bba..4e43e5ce3db 100644 --- a/common/src/main/java/org/apache/rocketmq/common/CountDownLatch2.java +++ b/common/src/main/java/org/apache/rocketmq/common/CountDownLatch2.java @@ -172,10 +172,12 @@ 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 (; ; ) { diff --git a/common/src/main/java/org/apache/rocketmq/common/KeyBuilder.java b/common/src/main/java/org/apache/rocketmq/common/KeyBuilder.java new file mode 100644 index 00000000000..e1532d9399b --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/KeyBuilder.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + + public static String buildPopRetryTopic(String topic, String cid) { + return MixAll.RETRY_GROUP_TOPIC_PREFIX + cid + "_" + topic; + } + + public static String parseNormalTopic(String topic, String cid) { + if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { + return topic.substring((MixAll.RETRY_GROUP_TOPIC_PREFIX + cid + "_").length()); + } else { + return topic; + } + } + + public static String buildPollingKey(String topic, String cid, int queueId) { + return topic + PopAckConstants.SPLIT + cid + PopAckConstants.SPLIT + queueId; + } + + public static String buildPollingNotificationKey(String topic, int queueId) { + return topic + PopAckConstants.SPLIT + queueId; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/LifecycleAwareServiceThread.java b/common/src/main/java/org/apache/rocketmq/common/LifecycleAwareServiceThread.java new file mode 100644 index 00000000000..a4fe5da812f --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/LifecycleAwareServiceThread.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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)); + } + } + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/LockCallback.java b/common/src/main/java/org/apache/rocketmq/common/LockCallback.java new file mode 100644 index 00000000000..9abf36755dc --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/LockCallback.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/common/src/main/java/org/apache/rocketmq/common/MQVersion.java b/common/src/main/java/org/apache/rocketmq/common/MQVersion.java index 3cf24e33a3b..bfd07a8959c 100644 --- a/common/src/main/java/org/apache/rocketmq/common/MQVersion.java +++ b/common/src/main/java/org/apache/rocketmq/common/MQVersion.java @@ -18,7 +18,7 @@ public class MQVersion { - public static final int CURRENT_VERSION = Version.V4_9_3.ordinal(); + public static final int CURRENT_VERSION = Version.V5_1_3.ordinal(); public static String getVersionDesc(int value) { int length = Version.values().length; diff --git a/common/src/main/java/org/apache/rocketmq/common/MixAll.java b/common/src/main/java/org/apache/rocketmq/common/MixAll.java index c2300d362c8..1233a54223b 100644 --- a/common/src/main/java/org/apache/rocketmq/common/MixAll.java +++ b/common/src/main/java/org/apache/rocketmq/common/MixAll.java @@ -19,7 +19,6 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; @@ -32,29 +31,37 @@ import java.net.SocketException; import java.net.URL; import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; 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.atomic.AtomicLong; +import java.util.function.Predicate; + +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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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"; 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"); - //http://jmenv.tbsite.net:8080/rocketmq/nsaddr - //public static final String WS_ADDR = "http://" + WS_DOMAIN_NAME + ":8080/rocketmq/" + WS_DOMAIN_SUBGROUP; 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"; @@ -69,11 +76,19 @@ public class MixAll { 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"; @@ -84,8 +99,41 @@ public class MixAll { 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 long LMQ_QUEUE_ID = 0; public static final String MULTI_DISPATCH_QUEUE_SPLITTER = ","; - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); + 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"; + + 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 boolean isWindows() { + return OS.indexOf("win") >= 0; + } + + public static boolean isMac() { + return OS.indexOf("mac") >= 0; + } + + public static boolean isUnix() { + return OS.indexOf("nix") >= 0 + || OS.indexOf("nux") >= 0 + || OS.indexOf("aix") > 0; + } + + public static boolean isSolaris() { + return OS.indexOf("sunos") >= 0; + } public static String getWSAddr() { String wsDomainName = System.getProperty("rocketmq.namesrv.domain", DEFAULT_NAMESRV_ADDR_LOOKUP); @@ -127,7 +175,7 @@ public static String brokerVIPChannel(final boolean isChange, final String broke public static long getPID() { String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); - if (processName != null && processName.length() > 0) { + if (StringUtils.isNotEmpty(processName)) { try { return Long.parseLong(processName.split("@")[0]); } catch (Exception e) { @@ -138,10 +186,7 @@ public static long getPID() { return 0; } - public static void string2File(final String str, final String fileName) throws IOException { - - String tmpFile = fileName + ".tmp"; - string2FileNotSafe(str, tmpFile); + public static synchronized void string2File(final String str, final String fileName) throws IOException { String bakFile = fileName + ".bak"; String prevContent = file2String(fileName); @@ -149,11 +194,7 @@ public static void string2File(final String str, final String fileName) throws I string2FileNotSafe(prevContent, bakFile); } - File file = new File(fileName); - file.delete(); - - file = new File(tmpFile); - file.renameTo(new File(fileName)); + string2FileNotSafe(str, fileName); } public static void string2FileNotSafe(final String str, final String fileName) throws IOException { @@ -162,18 +203,7 @@ public static void string2FileNotSafe(final String str, final String fileName) t if (fileParent != null) { fileParent.mkdirs(); } - FileWriter fileWriter = null; - - try { - fileWriter = new FileWriter(file); - fileWriter.write(str); - } catch (IOException e) { - throw e; - } finally { - if (fileWriter != null) { - fileWriter.close(); - } - } + IOTinyUtils.writeStringToFile(file, str, "UTF-8"); } public static String file2String(final String fileName) throws IOException { @@ -186,19 +216,13 @@ public static String file2String(final File file) throws IOException { byte[] data = new byte[(int) file.length()]; boolean result; - FileInputStream inputStream = null; - try { - inputStream = new FileInputStream(file); + try (FileInputStream inputStream = new FileInputStream(file)) { int len = inputStream.read(data); result = len == data.length; - } finally { - if (inputStream != null) { - inputStream.close(); - } } if (result) { - return new String(data); + return new String(data, "UTF-8"); } } return null; @@ -213,7 +237,7 @@ public static String file2String(final URL url) { int len = in.available(); byte[] data = new byte[len]; in.read(data, 0, len); - return new String(data, "UTF-8"); + return new String(data, StandardCharsets.UTF_8); } catch (Exception ignored) { } finally { if (null != in) { @@ -227,17 +251,24 @@ public static String file2String(final URL url) { return null; } - public static void printObjectProperties(final InternalLogger logger, final Object object) { + public static void printObjectProperties(final Logger logger, final Object object) { printObjectProperties(logger, object, false); } - public static void printObjectProperties(final InternalLogger logger, final Object object, + 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); @@ -249,16 +280,8 @@ public static void printObjectProperties(final InternalLogger logger, final Obje log.error("Failed to obtain object properties", e); } - if (onlyImportantField) { - Annotation annotation = field.getAnnotation(ImportantField.class); - if (null == annotation) { - continue; - } - } - if (logger != null) { logger.info(name + "=" + value); - } else { } } } @@ -266,8 +289,13 @@ public static void printObjectProperties(final InternalLogger logger, final Obje } 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(); - for (Map.Entry entry : properties.entrySet()) { + 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"); } @@ -291,24 +319,31 @@ public static Properties string2Properties(final String str) { public static Properties object2Properties(final Object object) { Properties properties = new Properties(); - Field[] fields = object.getClass().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); - } + 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 (value != null) { + properties.setProperty(name, value.toString()); + } } } } + if (objectClass == Object.class || objectClass.getSuperclass() == Object.class) { + break; + } + objectClass = objectClass.getSuperclass(); } return properties; @@ -358,8 +393,12 @@ 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(); + List inetAddressList = new ArrayList<>(); try { Enumeration enumeration = NetworkInterface.getNetworkInterfaces(); while (enumeration.hasMoreElements()) { @@ -394,7 +433,7 @@ private static String localhost() { //Reverse logic comparing to RemotingUtil method, consider refactor in RocketMQ 5.0 public static String getLocalhostByNetworkInterface() throws SocketException { - List candidatesHost = new ArrayList(); + List candidatesHost = new ArrayList<>(); Enumeration enumeration = NetworkInterface.getNetworkInterfaces(); while (enumeration.hasMoreElements()) { @@ -446,7 +485,37 @@ public static String humanReadableByteCount(long bytes, boolean si) { 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 isSysConsumerGroupForNoColdReadLimit(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; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/PlainAccessConfig.java b/common/src/main/java/org/apache/rocketmq/common/PlainAccessConfig.java index b193f43711f..24596daa529 100644 --- a/common/src/main/java/org/apache/rocketmq/common/PlainAccessConfig.java +++ b/common/src/main/java/org/apache/rocketmq/common/PlainAccessConfig.java @@ -16,9 +16,12 @@ */ package org.apache.rocketmq.common; +import java.io.Serializable; import java.util.List; +import java.util.Objects; -public class PlainAccessConfig { +public class PlainAccessConfig implements Serializable { + private static final long serialVersionUID = -4517357000307227637L; private String accessKey; @@ -99,4 +102,30 @@ public List getGroupPerms() { 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); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/PopAckConstants.java b/common/src/main/java/org/apache/rocketmq/common/PopAckConstants.java new file mode 100644 index 00000000000..2f979fa15f0 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/PopAckConstants.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/ServiceThread.java b/common/src/main/java/org/apache/rocketmq/common/ServiceThread.java index ead8117d6ba..95dc8b9800b 100644 --- a/common/src/main/java/org/apache/rocketmq/common/ServiceThread.java +++ b/common/src/main/java/org/apache/rocketmq/common/ServiceThread.java @@ -18,16 +18,17 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; + import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public abstract class ServiceThread implements Runnable { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private static final long JOIN_TIME = 90 * 1000; - private Thread thread; + protected Thread thread; protected final CountDownLatch2 waitPoint = new CountDownLatch2(1); protected volatile AtomicBoolean hasNotified = new AtomicBoolean(false); protected volatile boolean stopped = false; @@ -51,6 +52,7 @@ public void start() { 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() { @@ -63,11 +65,10 @@ public void shutdown(final boolean interrupt) { return; } this.stopped = true; - log.info("shutdown thread " + this.getServiceName() + " interrupt " + interrupt); + log.info("shutdown thread[{}] interrupt={} ", getServiceName(), interrupt); - if (hasNotified.compareAndSet(false, true)) { - waitPoint.countDown(); // notify - } + //if thead is waiting, wakeup it + wakeup(); try { if (interrupt) { @@ -76,48 +77,25 @@ public void shutdown(final boolean interrupt) { long beginTime = System.currentTimeMillis(); if (!this.thread.isDaemon()) { - this.thread.join(this.getJointime()); + this.thread.join(this.getJoinTime()); } long elapsedTime = System.currentTimeMillis() - beginTime; - log.info("join thread " + this.getServiceName() + " elapsed time(ms) " + elapsedTime + " " - + this.getJointime()); + log.info("join thread[{}], elapsed time: {}ms, join time:{}ms", getServiceName(), elapsedTime, this.getJoinTime()); } catch (InterruptedException e) { log.error("Interrupted", e); } } - public long getJointime() { + public long getJoinTime() { return JOIN_TIME; } - @Deprecated - public void stop() { - this.stop(false); - } - - @Deprecated - public void stop(final boolean interrupt) { - if (!started.get()) { - return; - } - this.stopped = true; - log.info("stop thread " + this.getServiceName() + " interrupt " + interrupt); - - if (hasNotified.compareAndSet(false, true)) { - waitPoint.countDown(); // notify - } - - if (interrupt) { - this.thread.interrupt(); - } - } - public void makeStop() { if (!started.get()) { return; } this.stopped = true; - log.info("makestop thread " + this.getServiceName()); + log.info("makestop thread[{}] ", this.getServiceName()); } public void wakeup() { diff --git a/common/src/main/java/org/apache/rocketmq/common/SubscriptionGroupAttributes.java b/common/src/main/java/org/apache/rocketmq/common/SubscriptionGroupAttributes.java new file mode 100644 index 00000000000..5b0072401c5 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/SubscriptionGroupAttributes.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.apache.rocketmq.common.attribute.Attribute; + +public class SubscriptionGroupAttributes { + public static final Map ALL; + + static { + ALL = new HashMap<>(); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/ThreadFactoryImpl.java b/common/src/main/java/org/apache/rocketmq/common/ThreadFactoryImpl.java index 564d60c54e4..bb0d141da09 100644 --- a/common/src/main/java/org/apache/rocketmq/common/ThreadFactoryImpl.java +++ b/common/src/main/java/org/apache/rocketmq/common/ThreadFactoryImpl.java @@ -19,8 +19,14 @@ 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; @@ -34,10 +40,29 @@ public ThreadFactoryImpl(final String threadNamePrefix, boolean daemon) { 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; } } diff --git a/common/src/main/java/org/apache/rocketmq/common/TopicAttributes.java b/common/src/main/java/org/apache/rocketmq/common/TopicAttributes.java new file mode 100644 index 00000000000..1f26866e5b3 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/TopicAttributes.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.apache.rocketmq.common.attribute.Attribute; +import org.apache.rocketmq.common.attribute.EnumAttribute; +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 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); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/TopicConfig.java b/common/src/main/java/org/apache/rocketmq/common/TopicConfig.java index 4795cced62d..0bf64905a03 100644 --- a/common/src/main/java/org/apache/rocketmq/common/TopicConfig.java +++ b/common/src/main/java/org/apache/rocketmq/common/TopicConfig.java @@ -16,12 +16,23 @@ */ package org.apache.rocketmq.common; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.alibaba.fastjson.annotation.JSONField; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.apache.rocketmq.common.attribute.TopicMessageType; import org.apache.rocketmq.common.constant.PermName; +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; @@ -29,6 +40,8 @@ public class TopicConfig { 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() { } @@ -37,6 +50,12 @@ 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; @@ -44,24 +63,53 @@ public TopicConfig(String topicName, int readQueueNums, int writeQueueNums, int 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 != null && strs.length == 5) { + if (strs.length >= 5) { this.topicName = strs[0]; this.readQueueNums = Integer.parseInt(strs[1]); @@ -72,6 +120,14 @@ public boolean decode(final String in) { 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; } @@ -134,29 +190,64 @@ 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()); + } + @Override - public boolean equals(final Object o) { - if (this == o) + public boolean equals(Object o) { + if (this == o) { return true; - if (o == null || getClass() != o.getClass()) + } + if (o == null || getClass() != o.getClass()) { return false; + } - final TopicConfig that = (TopicConfig) o; + TopicConfig that = (TopicConfig) o; - if (readQueueNums != that.readQueueNums) + if (readQueueNums != that.readQueueNums) { return false; - if (writeQueueNums != that.writeQueueNums) + } + if (writeQueueNums != that.writeQueueNums) { return false; - if (perm != that.perm) + } + if (perm != that.perm) { return false; - if (topicSysFlag != that.topicSysFlag) + } + if (topicSysFlag != that.topicSysFlag) { return false; - if (order != that.order) + } + if (order != that.order) { return false; - if (topicName != null ? !topicName.equals(that.topicName) : that.topicName != null) + } + if (!Objects.equals(topicName, that.topicName)) { return false; - return topicFilterType == that.topicFilterType; - + } + if (topicFilterType != that.topicFilterType) { + return false; + } + return Objects.equals(attributes, that.attributes); } @Override @@ -168,6 +259,7 @@ public int hashCode() { 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; } @@ -175,7 +267,7 @@ public int hashCode() { public String toString() { return "TopicConfig [topicName=" + topicName + ", readQueueNums=" + readQueueNums + ", writeQueueNums=" + writeQueueNums + ", perm=" + PermName.perm2String(perm) - + ", topicFilterType=" + topicFilterType + ", topicSysFlag=" + topicSysFlag + ", order=" - + order + "]"; + + ", topicFilterType=" + topicFilterType + ", topicSysFlag=" + topicSysFlag + ", order=" + order + + ", attributes=" + attributes + "]"; } } diff --git a/common/src/main/java/org/apache/rocketmq/common/TopicQueueId.java b/common/src/main/java/org/apache/rocketmq/common/TopicQueueId.java new file mode 100644 index 00000000000..c5f03877b2c --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/TopicQueueId.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/UnlockCallback.java b/common/src/main/java/org/apache/rocketmq/common/UnlockCallback.java new file mode 100644 index 00000000000..e86ec8b4af6 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/UnlockCallback.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/common/src/main/java/org/apache/rocketmq/common/UtilAll.java b/common/src/main/java/org/apache/rocketmq/common/UtilAll.java index a15b4fadf6d..d2b7c374b74 100644 --- a/common/src/main/java/org/apache/rocketmq/common/UtilAll.java +++ b/common/src/main/java/org/apache/rocketmq/common/UtilAll.java @@ -21,10 +21,16 @@ import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; +import java.lang.reflect.Field; +import java.lang.reflect.Method; 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.security.AccessController; +import java.security.PrivilegedAction; import java.text.NumberFormat; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -35,42 +41,62 @@ 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 org.apache.commons.lang3.JavaVersion; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.SystemUtils; import org.apache.commons.validator.routines.InetAddressValidator; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.remoting.common.RemotingHelper; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +import sun.misc.Unsafe; +import sun.nio.ch.DirectBuffer; public class UtilAll { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); + 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"; - final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - final static String HOST_NAME = ManagementFactory.getRuntimeMXBean().getName(); // format: "pid@hostname" + 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() { - try { - return Integer.parseInt(HOST_NAME.substring(0, HOST_NAME.indexOf('@'))); - } catch (Exception e) { - return -1; - } + return PID; } public static void sleep(long sleepMs) { - if (sleepMs < 0) { + sleep(sleepMs, TimeUnit.MILLISECONDS); + } + + public static void sleep(long timeOut, TimeUnit timeUnit) { + if (null == timeUnit) { return; } try { - Thread.sleep(sleepMs); + timeUnit.sleep(timeOut); } catch (Throwable ignored) { } - } public static String currentStackTrace() { @@ -196,6 +222,19 @@ public static String timeMillisToHumanString3(final long t) { 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(); @@ -203,20 +242,18 @@ public static boolean isPathExists(final String path) { public static double getDiskPartitionSpaceUsedPercent(final String path) { if (null == path || path.isEmpty()) { - log.error("Error when measuring disk space usage, path is null or empty, path : {}", path); + 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()) { - log.error("Error when measuring disk space usage, file doesn't exist on this path: {}", path); + 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) { @@ -231,13 +268,31 @@ public static double getDiskPartitionSpaceUsedPercent(final String path) { return result / 100.0; } } catch (Exception e) { - log.error("Error when measuring disk space usage, got 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); @@ -295,6 +350,10 @@ 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]; @@ -335,6 +394,10 @@ public static byte[] uncompress(final byte[] src) throws IOException { 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); @@ -405,16 +468,7 @@ public static String frontStringAtLeast(final String str, final int size) { } public static boolean isBlank(String str) { - int strLen; - if (str == null || (strLen = str.length()) == 0) { - return true; - } - for (int i = 0; i < strLen; i++) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; + return StringUtils.isBlank(str); } public static String jstack() { @@ -439,12 +493,27 @@ public static String jstack(Map map) { } } } catch (Throwable e) { - result.append(RemotingHelper.exceptionSimpleDesc(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"); @@ -453,8 +522,10 @@ public static boolean isInternalIP(byte[] ip) { //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) { if (ip[1] >= (byte) 16 && ip[1] <= (byte) 31) { @@ -501,7 +572,7 @@ public static String ipToIPv4Str(byte[] ip) { return null; } return new StringBuilder().append(ip[0] & 0xFF).append(".").append( - ip[1] & 0xFF).append(".").append(ip[2] & 0xFF) + ip[1] & 0xFF).append(".").append(ip[2] & 0xFF) .append(".").append(ip[3] & 0xFF).toString(); } @@ -540,7 +611,7 @@ public static byte[] getIP() { if (ipCheck(ipByte)) { if (!isInternalIP(ipByte)) { return ipByte; - } else if (internalIP == null) { + } else if (internalIP == null || internalIP[0] == (byte) 127) { internalIP = ipByte; } } @@ -606,4 +677,119 @@ public static List split(String str, String splitter) { 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()); + } + } + + public static void cleanBuffer(final ByteBuffer buffer) { + if (buffer == null || !buffer.isDirect() || buffer.capacity() == 0) { + return; + } + if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_9)) { + try { + Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + Unsafe unsafe = (Unsafe) field.get(null); + Method cleaner = method(unsafe, "invokeCleaner", new Class[] {ByteBuffer.class}); + cleaner.invoke(unsafe, viewed(buffer)); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } else { + invoke(invoke(viewed(buffer), "cleaner"), "clean"); + } + } + + public static Object invoke(final Object target, final String methodName, final Class... args) { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + try { + Method method = method(target, methodName, args); + method.setAccessible(true); + return method.invoke(target); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + }); + } + + public static Method method(Object target, String methodName, Class[] args) throws NoSuchMethodException { + try { + return target.getClass().getMethod(methodName, args); + } catch (NoSuchMethodException e) { + return target.getClass().getDeclaredMethod(methodName, args); + } + } + + private static ByteBuffer viewed(ByteBuffer buffer) { + if (!buffer.isDirect()) { + throw new IllegalArgumentException("buffer is non-direct"); + } + ByteBuffer viewedBuffer = (ByteBuffer) ((DirectBuffer) buffer).attachment(); + if (viewedBuffer == null) { + return buffer; + } else { + return viewed(viewedBuffer); + } + } + + 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; + } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionRecord.java b/common/src/main/java/org/apache/rocketmq/common/attribute/Attribute.java similarity index 58% rename from broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionRecord.java rename to common/src/main/java/org/apache/rocketmq/common/attribute/Attribute.java index 772f08e52c7..ba9be3bb43d 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionRecord.java +++ b/common/src/main/java/org/apache/rocketmq/common/attribute/Attribute.java @@ -14,30 +14,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.broker.transaction; +package org.apache.rocketmq.common.attribute; -/** - * This class will be removed in the version 4.4.0 and {@link OperationResult} class is recommended. - */ -@Deprecated -public class TransactionRecord { - // Commit Log Offset - private long offset; - private String producerGroup; +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 long getOffset() { - return offset; + public String getName() { + return name; } - public void setOffset(long offset) { - this.offset = offset; + public void setName(String name) { + this.name = name; } - public String getProducerGroup() { - return producerGroup; + public boolean isChangeable() { + return changeable; } - public void setProducerGroup(String producerGroup) { - this.producerGroup = producerGroup; + public void setChangeable(boolean changeable) { + this.changeable = changeable; } } diff --git a/common/src/main/java/org/apache/rocketmq/common/attribute/AttributeParser.java b/common/src/main/java/org/apache/rocketmq/common/attribute/AttributeParser.java new file mode 100644 index 00000000000..da98c6dab85 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/attribute/AttributeParser.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/attribute/AttributeUtil.java b/common/src/main/java/org/apache/rocketmq/common/attribute/AttributeUtil.java new file mode 100644 index 00000000000..a3646988c51 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/attribute/AttributeUtil.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/attribute/BooleanAttribute.java b/common/src/main/java/org/apache/rocketmq/common/attribute/BooleanAttribute.java new file mode 100644 index 00000000000..41ad7480547 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/attribute/BooleanAttribute.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/attribute/CQType.java b/common/src/main/java/org/apache/rocketmq/common/attribute/CQType.java new file mode 100644 index 00000000000..73ef2188009 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/attribute/CQType.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 +} diff --git a/common/src/main/java/org/apache/rocketmq/common/attribute/CleanupPolicy.java b/common/src/main/java/org/apache/rocketmq/common/attribute/CleanupPolicy.java new file mode 100644 index 00000000000..5f289a0a759 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/attribute/CleanupPolicy.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 +} diff --git a/common/src/main/java/org/apache/rocketmq/common/attribute/EnumAttribute.java b/common/src/main/java/org/apache/rocketmq/common/attribute/EnumAttribute.java new file mode 100644 index 00000000000..5353b8a293d --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/attribute/EnumAttribute.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/attribute/LongRangeAttribute.java b/common/src/main/java/org/apache/rocketmq/common/attribute/LongRangeAttribute.java new file mode 100644 index 00000000000..eeeda72153b --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/attribute/LongRangeAttribute.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/attribute/TopicMessageType.java b/common/src/main/java/org/apache/rocketmq/common/attribute/TopicMessageType.java new file mode 100644 index 00000000000..77629e4c90a --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/attribute/TopicMessageType.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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"); + + 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); + } + + public String getValue() { + return value; + } + + public static TopicMessageType parseFromMessageProperty(Map messageProperty) { + String isTrans = messageProperty.get(MessageConst.PROPERTY_TRANSACTION_PREPARED); + String isTransValue = "true"; + if (isTransValue.equals(isTrans)) { + 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) { + return TopicMessageType.DELAY; + } else if (messageProperty.get(MessageConst.PROPERTY_SHARDING_KEY) != null) { + return TopicMessageType.FIFO; + } + return TopicMessageType.NORMAL; + } + + public String getMetricsValue() { + return value.toLowerCase(); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/coldctr/AccAndTimeStamp.java b/common/src/main/java/org/apache/rocketmq/common/coldctr/AccAndTimeStamp.java new file mode 100644 index 00000000000..212bc08c485 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/coldctr/AccAndTimeStamp.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + + '}'; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/compression/CompressionType.java b/common/src/main/java/org/apache/rocketmq/common/compression/CompressionType.java new file mode 100644 index 00000000000..d748fc47a2d --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/compression/CompressionType.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/compression/Compressor.java b/common/src/main/java/org/apache/rocketmq/common/compression/Compressor.java new file mode 100644 index 00000000000..c900045b247 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/compression/Compressor.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; +} diff --git a/common/src/main/java/org/apache/rocketmq/common/compression/CompressorFactory.java b/common/src/main/java/org/apache/rocketmq/common/compression/CompressorFactory.java new file mode 100644 index 00000000000..aace1d956a1 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/compression/CompressorFactory.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + +} diff --git a/common/src/main/java/org/apache/rocketmq/common/compression/Lz4Compressor.java b/common/src/main/java/org/apache/rocketmq/common/compression/Lz4Compressor.java new file mode 100644 index 00000000000..0bcb9689dda --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/compression/Lz4Compressor.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/compression/ZlibCompressor.java b/common/src/main/java/org/apache/rocketmq/common/compression/ZlibCompressor.java new file mode 100644 index 00000000000..e64db9b62a6 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/compression/ZlibCompressor.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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) { + defeater.end(); + 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/compression/ZstdCompressor.java b/common/src/main/java/org/apache/rocketmq/common/compression/ZstdCompressor.java new file mode 100644 index 00000000000..131035c264e --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/compression/ZstdCompressor.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} \ No newline at end of file diff --git a/common/src/main/java/org/apache/rocketmq/common/consistenthash/ConsistentHashRouter.java b/common/src/main/java/org/apache/rocketmq/common/consistenthash/ConsistentHashRouter.java index fca1d877d45..08a58856fb6 100644 --- a/common/src/main/java/org/apache/rocketmq/common/consistenthash/ConsistentHashRouter.java +++ b/common/src/main/java/org/apache/rocketmq/common/consistenthash/ConsistentHashRouter.java @@ -16,6 +16,7 @@ */ package org.apache.rocketmq.common.consistenthash; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Collection; @@ -29,7 +30,7 @@ * algorithm */ public class ConsistentHashRouter { - private final SortedMap> ring = new TreeMap>(); + private final SortedMap> ring = new TreeMap<>(); private final HashFunction hashFunction; public ConsistentHashRouter(Collection pNodes, int vNodeCount) { @@ -64,7 +65,7 @@ public void addNode(T pNode, int vNodeCount) { 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); + VirtualNode vNode = new VirtualNode<>(pNode, i + existingReplicas); ring.put(hashFunction.hash(vNode.getKey()), vNode); } } @@ -122,7 +123,7 @@ public MD5Hash() { @Override public long hash(String key) { instance.reset(); - instance.update(key.getBytes()); + instance.update(key.getBytes(StandardCharsets.UTF_8)); byte[] digest = instance.digest(); long h = 0; diff --git a/common/src/main/java/org/apache/rocketmq/common/constant/ConsumeInitMode.java b/common/src/main/java/org/apache/rocketmq/common/constant/ConsumeInitMode.java new file mode 100644 index 00000000000..b7091fa2767 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/constant/ConsumeInitMode.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; +} diff --git a/common/src/main/java/org/apache/rocketmq/common/constant/FIleReadaheadMode.java b/common/src/main/java/org/apache/rocketmq/common/constant/FIleReadaheadMode.java new file mode 100644 index 00000000000..e23a5f58789 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/constant/FIleReadaheadMode.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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"; +} \ No newline at end of file diff --git a/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java b/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java index fe0ae9f1713..c1176ea1534 100644 --- a/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java +++ b/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java @@ -19,10 +19,14 @@ 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_NAME = "RocketmqNamesrvConsole"; + public static final String NAMESRV_CONSOLE_LOGGER_NAME = "RocketmqNamesrvConsole"; + public static final String CONTROLLER_LOGGER_NAME = "RocketmqController"; + 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"; @@ -30,11 +34,21 @@ public class LoggerName { 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 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"; } diff --git a/common/src/main/java/org/apache/rocketmq/common/constant/PermName.java b/common/src/main/java/org/apache/rocketmq/common/constant/PermName.java index 1fe9aa106e9..d7c76b4c0ea 100644 --- a/common/src/main/java/org/apache/rocketmq/common/constant/PermName.java +++ b/common/src/main/java/org/apache/rocketmq/common/constant/PermName.java @@ -17,10 +17,16 @@ package org.apache.rocketmq.common.constant; public class PermName { - public static final int PERM_PRIORITY = 0x1 << 3; - public static final int PERM_READ = 0x1 << 2; - public static final int PERM_WRITE = 0x1 << 1; - public static final int PERM_INHERIT = 0x1; + 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("---"); @@ -50,4 +56,16 @@ public static boolean isWriteable(final int perm) { 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 >= PERM_INHERIT && perm < PERM_PRIORITY; + } + + public static boolean isPriority(final int perm) { + return (perm & PERM_PRIORITY) == PERM_PRIORITY; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/consumer/ReceiptHandle.java b/common/src/main/java/org/apache/rocketmq/common/consumer/ReceiptHandle.java new file mode 100644 index 00000000000..392a3ae3395 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/consumer/ReceiptHandle.java @@ -0,0 +1,232 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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"; + 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); + } + + public String getRealTopic(String topic, String groupName) { + if (isRetryTopic()) { + return KeyBuilder.buildPopRetryTopic(topic, groupName); + } + return topic; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/fastjson/GenericMapSuperclassDeserializer.java b/common/src/main/java/org/apache/rocketmq/common/fastjson/GenericMapSuperclassDeserializer.java new file mode 100644 index 00000000000..80a1554d123 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/fastjson/GenericMapSuperclassDeserializer.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.JSONException; +import com.alibaba.fastjson.parser.DefaultJSONParser; +import com.alibaba.fastjson.parser.JSONToken; +import com.alibaba.fastjson.parser.deserializer.MapDeserializer; +import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer; +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 ObjectDeserializer { + public static final GenericMapSuperclassDeserializer INSTANCE = new GenericMapSuperclassDeserializer(); + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { + Class clz = (Class) type; + Type genericSuperclass = clz.getGenericSuperclass(); + Map map; + try { + map = (Map) clz.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 (String.class == keyType) { + return (T) MapDeserializer.parseMap(parser, (Map) map, valueType, fieldName); + } else { + return (T) MapDeserializer.parseMap(parser, map, keyType, valueType, fieldName); + } + } + + @Override + public int getFastMatchToken() { + return JSONToken.LBRACE; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/filter/impl/PolishExpr.java b/common/src/main/java/org/apache/rocketmq/common/filter/impl/PolishExpr.java index cdc6187a148..e80073f5d40 100644 --- a/common/src/main/java/org/apache/rocketmq/common/filter/impl/PolishExpr.java +++ b/common/src/main/java/org/apache/rocketmq/common/filter/impl/PolishExpr.java @@ -38,8 +38,8 @@ public static List reversePolish(String expression) { * @return the compute result of Shunting-yard algorithm */ public static List reversePolish(List tokens) { - List segments = new ArrayList(); - Stack operatorStack = new Stack(); + List segments = new ArrayList<>(); + Stack operatorStack = new Stack<>(); for (int i = 0; i < tokens.size(); i++) { Op token = tokens.get(i); @@ -87,7 +87,7 @@ public static List reversePolish(List tokens) { * @throws Exception */ private static List participle(String expression) { - List segments = new ArrayList(); + List segments = new ArrayList<>(); int size = expression.length(); int wordStartIndex = -1; @@ -97,8 +97,8 @@ private static List participle(String expression) { 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 (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) { diff --git a/common/src/main/java/org/apache/rocketmq/common/future/FutureTaskExt.java b/common/src/main/java/org/apache/rocketmq/common/future/FutureTaskExt.java new file mode 100644 index 00000000000..1bd46eafa2c --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/future/FutureTaskExt.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/help/FAQUrl.java b/common/src/main/java/org/apache/rocketmq/common/help/FAQUrl.java index 5d950bebfb1..4a6588e4124 100644 --- a/common/src/main/java/org/apache/rocketmq/common/help/FAQUrl.java +++ b/common/src/main/java/org/apache/rocketmq/common/help/FAQUrl.java @@ -18,51 +18,39 @@ public class FAQUrl { - public static final String APPLY_TOPIC_URL = - "http://rocketmq.apache.org/docs/faq/"; + public static final String APPLY_TOPIC_URL = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; - public static final String NAME_SERVER_ADDR_NOT_EXIST_URL = - "http://rocketmq.apache.org/docs/faq/"; + public static final String NAME_SERVER_ADDR_NOT_EXIST_URL = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; - public static final String GROUP_NAME_DUPLICATE_URL = - "http://rocketmq.apache.org/docs/faq/"; + public static final String GROUP_NAME_DUPLICATE_URL = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; - public static final String CLIENT_PARAMETER_CHECK_URL = - "http://rocketmq.apache.org/docs/faq/"; + public static final String CLIENT_PARAMETER_CHECK_URL = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; - public static final String SUBSCRIPTION_GROUP_NOT_EXIST = - "http://rocketmq.apache.org/docs/faq/"; + public static final String SUBSCRIPTION_GROUP_NOT_EXIST = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; - public static final String CLIENT_SERVICE_NOT_OK = - "http://rocketmq.apache.org/docs/faq/"; + public static final String CLIENT_SERVICE_NOT_OK = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; // FAQ: No route info of this topic, TopicABC - public static final String NO_TOPIC_ROUTE_INFO = - "http://rocketmq.apache.org/docs/faq/"; + public static final String NO_TOPIC_ROUTE_INFO = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; - public static final String LOAD_JSON_EXCEPTION = - "http://rocketmq.apache.org/docs/faq/"; + public static final String LOAD_JSON_EXCEPTION = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; - public static final String SAME_GROUP_DIFFERENT_TOPIC = - "http://rocketmq.apache.org/docs/faq/"; + public static final String SAME_GROUP_DIFFERENT_TOPIC = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; - public static final String MQLIST_NOT_EXIST = - "http://rocketmq.apache.org/docs/faq/"; + public static final String MQLIST_NOT_EXIST = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; - public static final String UNEXPECTED_EXCEPTION_URL = - "http://rocketmq.apache.org/docs/faq/"; + public static final String UNEXPECTED_EXCEPTION_URL = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; - public static final String SEND_MSG_FAILED = - "http://rocketmq.apache.org/docs/faq/"; + public static final String SEND_MSG_FAILED = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; - public static final String UNKNOWN_HOST_EXCEPTION = - "http://rocketmq.apache.org/docs/faq/"; + public static final String UNKNOWN_HOST_EXCEPTION = "https://rocketmq.apache.org/docs/bestPractice/06FAQ"; 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(); + 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); @@ -73,10 +61,10 @@ public static String attachDefaultURL(final String errorMessage) { if (errorMessage != null) { int index = errorMessage.indexOf(TIP_STRING_BEGIN); if (-1 == index) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(errorMessage.length() + UNEXPECTED_EXCEPTION_URL.length() + MORE_INFORMATION.length() + 1); sb.append(errorMessage); sb.append("\n"); - sb.append("For more information, please visit the url, "); + sb.append(MORE_INFORMATION); sb.append(UNEXPECTED_EXCEPTION_URL); return sb.toString(); } diff --git a/common/src/main/java/org/apache/rocketmq/common/logging/DefaultJoranConfiguratorExt.java b/common/src/main/java/org/apache/rocketmq/common/logging/DefaultJoranConfiguratorExt.java new file mode 100644 index 00000000000..19164e3c6d4 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/logging/DefaultJoranConfiguratorExt.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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() + "]"); + } + } + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/logging/JoranConfiguratorExt.java b/common/src/main/java/org/apache/rocketmq/common/logging/JoranConfiguratorExt.java new file mode 100644 index 00000000000..6995e9a4934 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/logging/JoranConfiguratorExt.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + } + } + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/message/Message.java b/common/src/main/java/org/apache/rocketmq/common/message/Message.java index 48e1f45a7f0..e02b526a184 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/Message.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/Message.java @@ -68,7 +68,7 @@ public void setKeys(String keys) { void putProperty(final String name, final String value) { if (null == this.properties) { - this.properties = new HashMap(); + this.properties = new HashMap<>(); } this.properties.put(name, value); @@ -102,7 +102,7 @@ public String getUserProperty(final String name) { public String getProperty(final String name) { if (null == this.properties) { - this.properties = new HashMap(); + this.properties = new HashMap<>(); } return this.properties.get(name); @@ -128,14 +128,10 @@ public String getKeys() { return this.getProperty(MessageConst.PROPERTY_KEYS); } - public void setKeys(Collection keys) { - StringBuilder sb = new StringBuilder(); - for (String k : keys) { - sb.append(k); - sb.append(MessageConst.KEY_SEPARATOR); - } + public void setKeys(Collection keyCollection) { + String keys = String.join(MessageConst.KEY_SEPARATOR, keyCollection); - this.setKeys(sb.toString().trim()); + this.setKeys(keys); } public int getDelayTimeLevel() { @@ -218,4 +214,18 @@ public String toString() { ", transactionId='" + transactionId + '\'' + '}'; } + + public void setDelayTimeSec(long sec) { + this.putProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC, String.valueOf(sec)); + } + public void setDelayTimeMs(long timeMs) { + this.putProperty(MessageConst.PROPERTY_TIMER_DELAY_MS, String.valueOf(timeMs)); + } + public void setDeliverTimeMs(long timeMs) { + this.putProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS, String.valueOf(timeMs)); + } + + public long getDeliverTimeMs() { + return Long.parseLong(this.getUserProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS)); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageBatch.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageBatch.java index a6b801edab5..a423048c5cb 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/MessageBatch.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageBatch.java @@ -39,10 +39,10 @@ public Iterator iterator() { return messages.iterator(); } - public static MessageBatch generateFromList(Collection messages) { + public static MessageBatch generateFromList(Collection messages) { assert messages != null; assert messages.size() > 0; - List messageList = new ArrayList(messages.size()); + List messageList = new ArrayList<>(messages.size()); Message first = null; for (Message message : messages) { if (message.getDelayTimeLevel() > 0) { diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageClientIDSetter.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageClientIDSetter.java index 57090c17120..4ae5ef59d6d 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/MessageClientIDSetter.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageClientIDSetter.java @@ -23,7 +23,7 @@ import org.apache.rocketmq.common.UtilAll; public class MessageClientIDSetter { - private static final String TOPIC_KEY_SPLITTER = "#"; + private static final int LEN; private static final char[] FIX_STRING; private static final AtomicInteger COUNTER; diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageConst.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageConst.java index 81b7823f3b5..87fed7c192e 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/MessageConst.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageConst.java @@ -39,11 +39,16 @@ public class MessageConst { 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_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_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_CHECK_IMMUNITY_TIME_IN_SECONDS = "CHECK_IMMUNITY_TIME_IN_SECONDS"; 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"; @@ -52,13 +57,53 @@ public class MessageConst { 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_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 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_DELAY_LEVEL = "TIMER_DELAY_LEVEL"; + public static final String PROPERTY_TIMER_DELAY_MS = "TIMER_DELAY_MS"; + + /** + * 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); @@ -82,6 +127,12 @@ public class MessageConst { 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); @@ -91,5 +142,18 @@ public class MessageConst { 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); } } diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java index 929912772e5..6de0b69fb75 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java @@ -16,6 +16,7 @@ */ package org.apache.rocketmq.common.message; +import java.io.IOException; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -23,22 +24,31 @@ 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 = Charset.forName("UTF-8"); - public final static int MESSAGE_MAGIC_CODE_POSTION = 4; - public final static int MESSAGE_FLAG_POSTION = 16; - public final static int MESSAGE_PHYSIC_OFFSET_POSTION = 28; - // public final static int MESSAGE_STORE_TIMESTAMP_POSTION = 56; + 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; @@ -82,20 +92,17 @@ public static String createMessageId(SocketAddress socketAddress, long transacti } public static MessageId decodeMessageId(final String msgId) throws UnknownHostException { - SocketAddress address; - long offset; - int ipLength = msgId.length() == 32 ? 4 * 2 : 16 * 2; + byte[] bytes = UtilAll.string2bytes(msgId); + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); - byte[] ip = UtilAll.string2bytes(msgId.substring(0, ipLength)); - byte[] port = UtilAll.string2bytes(msgId.substring(ipLength, ipLength + 8)); - ByteBuffer bb = ByteBuffer.wrap(port); - int portInt = bb.getInt(0); - address = new InetSocketAddress(InetAddress.getByAddress(ip), portInt); + // 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 - byte[] data = UtilAll.string2bytes(msgId.substring(ipLength + 8, ipLength + 8 + 16)); - bb = ByteBuffer.wrap(data); - offset = bb.getLong(0); + long offset = byteBuffer.getLong(); return new MessageId(address, offset); } @@ -107,6 +114,9 @@ public static MessageId decodeMessageId(final String msgId) throws UnknownHostEx */ 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 @@ -123,20 +133,21 @@ public static Map decodeProperties(ByteBuffer byteBuffer) { + storehostAddressLength // 12 STOREHOSTADDRESS + 4 // 13 RECONSUMETIMES + 8; // 14 Prepared Transaction Offset - int topicLengthPosition = bodySizePosition + 4 + byteBuffer.getInt(bodySizePosition); - byte topicLength = byteBuffer.get(topicLengthPosition); - - short propertiesLength = byteBuffer.getShort(topicLengthPosition + 1 + topicLength); + int topicLengthPosition = bodySizePosition + 4 + byteBuffer.getInt(bodySizePosition); + byteBuffer.position(topicLengthPosition); + int topicLengthSize = version.getTopicLengthSize(); + int topicLength = version.getTopicLength(byteBuffer); - byteBuffer.position(topicLengthPosition + 1 + topicLength + 2); + 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); - Map map = string2messageProperties(propertiesString); - return map; + return string2messageProperties(propertiesString); } return null; } @@ -165,7 +176,8 @@ public static byte[] encode(MessageExt messageExt, boolean needCompress) throws int storehostAddressLength = (sysFlag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 8 : 20; byte[] newBody = messageExt.getBody(); if (needCompress && (sysFlag & MessageSysFlag.COMPRESSED_FLAG) == MessageSysFlag.COMPRESSED_FLAG) { - newBody = UtilAll.compress(body, 5); + Compressor compressor = CompressorFactory.getCompressor(MessageSysFlag.getCompressionType(sysFlag)); + newBody = compressor.compress(body, 5); } int bodyLength = newBody.length; int storeSize = messageExt.getStoreSize(); @@ -263,13 +275,132 @@ public static byte[] encode(MessageExt messageExt, boolean needCompress) throws 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( - ByteBuffer byteBuffer, final boolean readBody, final boolean deCompressBody, final boolean isClient) { + 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; @@ -284,7 +415,8 @@ public static MessageExt decode( msgExt.setStoreSize(storeSize); // 2 MAGICCODE - byteBuffer.getInt(); + int magicCode = byteBuffer.getInt(); + MessageVersion version = MessageVersion.valueOfMagicCode(magicCode); // 3 BODYCRC int bodyCRC = byteBuffer.getInt(); @@ -347,9 +479,18 @@ public static MessageExt decode( 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!"); + } + } + // uncompress body if (deCompressBody && (sysFlag & MessageSysFlag.COMPRESSED_FLAG) == MessageSysFlag.COMPRESSED_FLAG) { - body = UtilAll.uncompress(body); + Compressor compressor = CompressorFactory.getCompressor(MessageSysFlag.getCompressionType(sysFlag)); + body = compressor.decompress(body); } msgExt.setBody(body); @@ -359,8 +500,8 @@ public static MessageExt decode( } // 16 TOPIC - byte topicLen = byteBuffer.get(); - byte[] topic = new byte[(int) topicLen]; + int topicLen = version.getTopicLength(byteBuffer); + byte[] topic = new byte[topicLen]; byteBuffer.get(topic); msgExt.setTopic(new String(topic, CHARSET_UTF8)); @@ -370,8 +511,14 @@ public static MessageExt decode( byte[] properties = new byte[propertiesLength]; byteBuffer.get(properties); String propertiesString = new String(properties, CHARSET_UTF8); - Map map = string2messageProperties(propertiesString); - msgExt.setProperties(map); + 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; @@ -395,8 +542,24 @@ 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(); + List msgExts = new ArrayList<>(); while (byteBuffer.hasRemaining()) { MessageExt msgExt = clientDecode(byteBuffer, readBody); if (null != msgExt) { @@ -426,28 +589,26 @@ public static String messageProperties2String(Map properties) { len += 2; // separator } StringBuilder sb = new StringBuilder(len); - if (properties != null) { - for (final Map.Entry entry : properties.entrySet()) { - final String name = entry.getKey(); - final String value = entry.getValue(); + 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); - } - if (sb.length() > 0) { - sb.deleteCharAt(sb.length() - 1); + if (value == null) { + continue; } + sb.append(name); + sb.append(NAME_VALUE_SEPARATOR); + sb.append(value); + sb.append(PROPERTY_SEPARATOR); + } + if (sb.length() > 0) { + sb.deleteCharAt(sb.length() - 1); } return sb.toString(); } public static Map string2messageProperties(final String properties) { - Map map = new HashMap(); + Map map = new HashMap<>(128); if (properties != null) { int len = properties.length(); int index = 0; @@ -478,10 +639,7 @@ public static byte[] encodeMessage(Message message) { String properties = messageProperties2String(message.getProperties()); byte[] propertiesBytes = properties.getBytes(CHARSET_UTF8); //note properties length must not more than Short.MAX - int propsLen = propertiesBytes.length; - if (propsLen > Short.MAX_VALUE) - throw new RuntimeException(String.format("Properties size of message exceeded, properties size: {}, maxSize: {}.", propsLen, Short.MAX_VALUE)); - short propertiesLength = (short) propsLen; + short propertiesLength = (short) propertiesBytes.length; int sysFlag = message.getFlag(); int storeSize = 4 // 1 TOTALSIZE + 4 // 2 MAGICCOD @@ -547,7 +705,7 @@ public static Message decodeMessage(ByteBuffer byteBuffer) throws Exception { public static byte[] encodeMessages(List messages) { //TO DO refactor, accumulate in one buffer, avoid copies - List encodedMessages = new ArrayList(messages.size()); + List encodedMessages = new ArrayList<>(messages.size()); int allSize = 0; for (Message message : messages) { byte[] tmp = encodeMessage(message); @@ -565,11 +723,44 @@ public static byte[] encodeMessages(List messages) { public static List decodeMessages(ByteBuffer byteBuffer) throws Exception { //TO DO add a callback for processing, avoid creating lists - List msgs = new ArrayList(); + 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; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageExt.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageExt.java index 133cb93ba0f..5905d28f41b 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/MessageExt.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageExt.java @@ -249,6 +249,61 @@ 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 diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageExtBatch.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageExtBatch.java index a2713cb4c3d..42f98e45bd8 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/MessageExtBatch.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageExtBatch.java @@ -19,15 +19,28 @@ import java.nio.ByteBuffer; -public class MessageExtBatch extends MessageExt { +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() { diff --git a/store/src/main/java/org/apache/rocketmq/store/MessageExtBrokerInner.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageExtBrokerInner.java similarity index 87% rename from store/src/main/java/org/apache/rocketmq/store/MessageExtBrokerInner.java rename to common/src/main/java/org/apache/rocketmq/common/message/MessageExtBrokerInner.java index df7e6e586bb..0c72ebb7bbd 100644 --- a/store/src/main/java/org/apache/rocketmq/store/MessageExtBrokerInner.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageExtBrokerInner.java @@ -14,12 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.store; +package org.apache.rocketmq.common.message; import java.nio.ByteBuffer; import org.apache.rocketmq.common.TopicFilterType; -import org.apache.rocketmq.common.message.MessageExt; public class MessageExtBrokerInner extends MessageExt { private static final long serialVersionUID = 7256001576878700634L; @@ -28,6 +27,8 @@ public class MessageExtBrokerInner extends MessageExt { private ByteBuffer encodedBuff; + private MessageVersion version = MessageVersion.MESSAGE_VERSION_V1; + public ByteBuffer getEncodedBuff() { return encodedBuff; } @@ -61,4 +62,12 @@ public long getTagsCode() { public void setTagsCode(long tagsCode) { this.tagsCode = tagsCode; } + + public MessageVersion getVersion() { + return version; + } + + public void setVersion(MessageVersion version) { + this.version = version; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageQueue.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageQueue.java index 03ba2027e03..7926b7327d6 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/MessageQueue.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageQueue.java @@ -28,6 +28,12 @@ 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; diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageQueueAssignment.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageQueueAssignment.java new file mode 100644 index 00000000000..fcd9f5802e2 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageQueueAssignment.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } + +} diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageRequestMode.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageRequestMode.java new file mode 100644 index 00000000000..35a166a6761 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageRequestMode.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageType.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageType.java index 08091f47f19..5ffd2f65d0a 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/MessageType.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageType.java @@ -18,8 +18,28 @@ package org.apache.rocketmq.common.message; public enum MessageType { - Normal_Msg, - Trans_Msg_Half, - Trans_msg_Commit, - Delay_Msg, + 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; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageVersion.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageVersion.java new file mode 100644 index 00000000000..bb1c2e8d64b --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageVersion.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/common/src/main/java/org/apache/rocketmq/common/metrics/MetricsExporterType.java b/common/src/main/java/org/apache/rocketmq/common/metrics/MetricsExporterType.java new file mode 100644 index 00000000000..5f065b45342 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/metrics/MetricsExporterType.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/metrics/NopLongCounter.java b/common/src/main/java/org/apache/rocketmq/common/metrics/NopLongCounter.java new file mode 100644 index 00000000000..a281216ab34 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/metrics/NopLongCounter.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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) { + + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/metrics/NopLongHistogram.java b/common/src/main/java/org/apache/rocketmq/common/metrics/NopLongHistogram.java new file mode 100644 index 00000000000..e967c63f281 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/metrics/NopLongHistogram.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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) { + + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/metrics/NopLongUpDownCounter.java b/common/src/main/java/org/apache/rocketmq/common/metrics/NopLongUpDownCounter.java new file mode 100644 index 00000000000..3e8be197641 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/metrics/NopLongUpDownCounter.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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) { + + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/metrics/NopObservableLongGauge.java b/common/src/main/java/org/apache/rocketmq/common/metrics/NopObservableLongGauge.java new file mode 100644 index 00000000000..091fa72de63 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/metrics/NopObservableLongGauge.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 { +} diff --git a/common/src/main/java/org/apache/rocketmq/common/namesrv/DefaultTopAddressing.java b/common/src/main/java/org/apache/rocketmq/common/namesrv/DefaultTopAddressing.java new file mode 100644 index 00000000000..179e200ae91 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/namesrv/DefaultTopAddressing.java @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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) { + String url = this.wsAddr; + try { + if (null != para && para.size() > 0) { + if (!UtilAll.isBlank(this.unitName)) { + url = url + "-" + this.unitName + "?nofix=1&"; + } + else { + url = url + "?"; + } + for (Map.Entry entry : this.para.entrySet()) { + url += entry.getKey() + "=" + entry.getValue() + "&"; + } + url = url.substring(0, url.length() - 1); + } + else { + if (!UtilAll.isBlank(this.unitName)) { + url = url + "-" + this.unitName + "?nofix=1"; + } + } + + HttpTinyClient.HttpResult result = HttpTinyClient.httpGet(url, 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/namesrv/NameServerUpdateCallback.java b/common/src/main/java/org/apache/rocketmq/common/namesrv/NameServerUpdateCallback.java new file mode 100644 index 00000000000..67ce2b86823 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/namesrv/NameServerUpdateCallback.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/common/src/main/java/org/apache/rocketmq/common/namesrv/NamesrvConfig.java b/common/src/main/java/org/apache/rocketmq/common/namesrv/NamesrvConfig.java index f687d2c2437..700febfe277 100644 --- a/common/src/main/java/org/apache/rocketmq/common/namesrv/NamesrvConfig.java +++ b/common/src/main/java/org/apache/rocketmq/common/namesrv/NamesrvConfig.java @@ -22,12 +22,8 @@ import java.io.File; import org.apache.rocketmq.common.MixAll; -import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; public class NamesrvConfig { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV)); private String kvConfigPath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "kvConfig.json"; @@ -35,6 +31,56 @@ public class NamesrvConfig { 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; public boolean isOrderMessageEnable() { return orderMessageEnable; @@ -83,4 +129,116 @@ public String getConfigStorePath() { 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; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/namesrv/TopAddressing.java b/common/src/main/java/org/apache/rocketmq/common/namesrv/TopAddressing.java index 88a4122b6d2..3ca182825b2 100644 --- a/common/src/main/java/org/apache/rocketmq/common/namesrv/TopAddressing.java +++ b/common/src/main/java/org/apache/rocketmq/common/namesrv/TopAddressing.java @@ -14,94 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/** - * $Id: TopAddressing.java 1831 2013-05-16 01:39:51Z vintagewang@apache.org $ - */ package org.apache.rocketmq.common.namesrv; -import java.io.IOException; -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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.utils.HttpTinyClient; - -public class TopAddressing { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); - - private String nsAddr; - private String wsAddr; - private String unitName; - - public TopAddressing(final String wsAddr) { - this(wsAddr, null); - } - - public TopAddressing(final String wsAddr, final String unitName) { - this.wsAddr = wsAddr; - this.unitName = unitName; - } - - 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; - } - - public final String fetchNSAddr() { - return fetchNSAddr(true, 3000); - } - - public final String fetchNSAddr(boolean verbose, long timeoutMills) { - String url = this.wsAddr; - try { - if (!UtilAll.isBlank(this.unitName)) { - url = url + "-" + this.unitName + "?nofix=1"; - } - HttpTinyClient.HttpResult result = HttpTinyClient.httpGet(url, null, null, "UTF-8", timeoutMills); - if (200 == result.code) { - String responseStr = result.content; - if (responseStr != null) { - return clearNewLine(responseStr); - } else { - log.error("fetch nameserver address is null"); - } - } else { - log.error("fetch nameserver address failed. statusCode=" + result.code); - } - } catch (IOException e) { - if (verbose) { - log.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); - log.warn(errorMsg); - } - return null; - } +public interface TopAddressing { - public String getNsAddr() { - return nsAddr; - } + String fetchNSAddr(); - public void setNsAddr(String nsAddr) { - this.nsAddr = nsAddr; - } + void registerChangeCallBack(NameServerUpdateCallback changeCallBack); } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/PullMessageRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/PullMessageRequestHeader.java deleted file mode 100644 index 106e89e511c..00000000000 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/PullMessageRequestHeader.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.common.protocol.header; - -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; - -public class PullMessageRequestHeader implements CommandCustomHeader { - @CFNotNull - private String consumerGroup; - @CFNotNull - private String topic; - @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; - - @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 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; - } -} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/PullMessageResponseHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/PullMessageResponseHeader.java deleted file mode 100644 index 0112f7da8d6..00000000000 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/PullMessageResponseHeader.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.common.protocol.header; - -import org.apache.rocketmq.remoting.CommandCustomHeader; -import org.apache.rocketmq.remoting.annotation.CFNotNull; -import org.apache.rocketmq.remoting.exception.RemotingCommandException; - -public class PullMessageResponseHeader implements CommandCustomHeader { - @CFNotNull - private Long suggestWhichBrokerId; - @CFNotNull - private Long nextBeginOffset; - @CFNotNull - private Long minOffset; - @CFNotNull - private Long maxOffset; - - @Override - public void checkFields() throws RemotingCommandException { - } - - 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; - } -} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/route/TopicRouteData.java b/common/src/main/java/org/apache/rocketmq/common/protocol/route/TopicRouteData.java deleted file mode 100644 index e8f54b8d73e..00000000000 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/route/TopicRouteData.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.common.protocol.route; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; - -public class TopicRouteData extends RemotingSerializable { - private String orderTopicConf; - private List queueDatas; - private List brokerDatas; - private HashMap/* Filter Server */> filterServerTable; - - public TopicRouteData cloneTopicRouteData() { - TopicRouteData topicRouteData = new TopicRouteData(); - topicRouteData.setQueueDatas(new ArrayList()); - topicRouteData.setBrokerDatas(new ArrayList()); - topicRouteData.setFilterServerTable(new HashMap>()); - topicRouteData.setOrderTopicConf(this.orderTopicConf); - - if (this.queueDatas != null) { - topicRouteData.getQueueDatas().addAll(this.queueDatas); - } - - if (this.brokerDatas != null) { - topicRouteData.getBrokerDatas().addAll(this.brokerDatas); - } - - if (this.filterServerTable != null) { - topicRouteData.getFilterServerTable().putAll(this.filterServerTable); - } - - return topicRouteData; - } - - 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; - } - - @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()); - 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; - return true; - } - - @Override - public String toString() { - return "TopicRouteData [orderTopicConf=" + orderTopicConf + ", queueDatas=" + queueDatas - + ", brokerDatas=" + brokerDatas + ", filterServerTable=" + filterServerTable + "]"; - } -} diff --git a/common/src/main/java/org/apache/rocketmq/common/queue/ConcurrentTreeMap.java b/common/src/main/java/org/apache/rocketmq/common/queue/ConcurrentTreeMap.java index 791c72c18a5..1df2f96c79b 100644 --- a/common/src/main/java/org/apache/rocketmq/common/queue/ConcurrentTreeMap.java +++ b/common/src/main/java/org/apache/rocketmq/common/queue/ConcurrentTreeMap.java @@ -22,21 +22,21 @@ import java.util.TreeMap; import java.util.concurrent.locks.ReentrantLock; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; /** * thread safe */ public class ConcurrentTreeMap { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + 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); + tree = new TreeMap<>(comparator); + roundQueue = new RoundQueue<>(capacity); lock = new ReentrantLock(true); } diff --git a/common/src/main/java/org/apache/rocketmq/common/queue/RoundQueue.java b/common/src/main/java/org/apache/rocketmq/common/queue/RoundQueue.java index a2bbe9dcd70..8fc5f68791f 100644 --- a/common/src/main/java/org/apache/rocketmq/common/queue/RoundQueue.java +++ b/common/src/main/java/org/apache/rocketmq/common/queue/RoundQueue.java @@ -30,7 +30,7 @@ public class RoundQueue { public RoundQueue(int capacity) { this.capacity = capacity; - queue = new LinkedList(); + queue = new LinkedList<>(); } public boolean put(E e) { diff --git a/common/src/main/java/org/apache/rocketmq/common/statistics/FutureHolder.java b/common/src/main/java/org/apache/rocketmq/common/statistics/FutureHolder.java new file mode 100644 index 00000000000..1fcf0b8bf04 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/statistics/FutureHolder.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + } + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/statistics/Interceptor.java b/common/src/main/java/org/apache/rocketmq/common/statistics/Interceptor.java new file mode 100644 index 00000000000..0af55e05910 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/statistics/Interceptor.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); +} diff --git a/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsBrief.java b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsBrief.java new file mode 100644 index 00000000000..970bff98a08 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsBrief.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsBriefInterceptor.java b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsBriefInterceptor.java new file mode 100644 index 00000000000..b0b69378442 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsBriefInterceptor.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 breifItemName: " + 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItem.java b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItem.java new file mode 100644 index 00000000000..23633dc5bf1 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItem.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } + +} diff --git a/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemFormatter.java b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemFormatter.java new file mode 100644 index 00000000000..f3a9bdb7205 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemFormatter.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemPrinter.java b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemPrinter.java new file mode 100644 index 00000000000..1f46590a10d --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemPrinter.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemScheduledIncrementPrinter.java b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemScheduledIncrementPrinter.java new file mode 100644 index 00000000000..2890e6e15cd --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemScheduledIncrementPrinter.java @@ -0,0 +1,290 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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} miliseconds + 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 inteceptor = item.getInterceptor(); + String inteceptorStr = formatInterceptor(inteceptor); + if (inteceptor != null) { + inteceptor.reset(); + } + + StatisticsItemSampleBrief brief = getSampleBrief(item.getStatKind(), item.getStatObject()); + if (brief != null && (!increment.allZeros() || printZeroLine())) { + printer.print(name, increment, inteceptorStr, brief.toString()); + } + + setItemSnapshot(lastItemSnapshots, snapshot); + + if (brief != null) { + brief.reset(); + } + } + }, getInitialDelay(), interval, TimeUnit.MILLISECONDS); + addFuture(item, future); + + // sample every TPS_INTREVAL + 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; + } + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemScheduledPrinter.java b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemScheduledPrinter.java new file mode 100644 index 00000000000..799c02ccb26 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemScheduledPrinter.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } + +} diff --git a/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemStateGetter.java b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemStateGetter.java new file mode 100644 index 00000000000..3b16d00e12b --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsItemStateGetter.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsKindMeta.java b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsKindMeta.java new file mode 100644 index 00000000000..27bee19b21b --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsKindMeta.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} \ No newline at end of file diff --git a/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsManager.java b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsManager.java new file mode 100644 index 00000000000..8d6bdb73a5e --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/statistics/StatisticsManager.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItem.java b/common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItem.java index bc987b191e3..d38281bf83a 100644 --- a/common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItem.java +++ b/common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItem.java @@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.common.UtilAll; -import org.apache.rocketmq.logging.InternalLogger; +import org.apache.rocketmq.logging.org.slf4j.Logger; public class MomentStatsItem { @@ -30,10 +30,10 @@ public class MomentStatsItem { private final String statsName; private final String statsKey; private final ScheduledExecutorService scheduledExecutorService; - private final InternalLogger log; + private final Logger log; public MomentStatsItem(String statsName, String statsKey, - ScheduledExecutorService scheduledExecutorService, InternalLogger log) { + ScheduledExecutorService scheduledExecutorService, Logger log) { this.statsName = statsName; this.statsKey = statsKey; this.scheduledExecutorService = scheduledExecutorService; diff --git a/common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItemSet.java b/common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItemSet.java index 4d2ce0cfcdc..a4571d7b8ad 100644 --- a/common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItemSet.java +++ b/common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItemSet.java @@ -24,16 +24,16 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.UtilAll; -import org.apache.rocketmq.logging.InternalLogger; +import org.apache.rocketmq.logging.org.slf4j.Logger; public class MomentStatsItemSet { private final ConcurrentMap statsItemTable = - new ConcurrentHashMap(128); + new ConcurrentHashMap<>(128); private final String statsName; private final ScheduledExecutorService scheduledExecutorService; - private final InternalLogger log; + private final Logger log; - public MomentStatsItemSet(String statsName, ScheduledExecutorService scheduledExecutorService, InternalLogger log) { + public MomentStatsItemSet(String statsName, ScheduledExecutorService scheduledExecutorService, Logger log) { this.statsName = statsName; this.scheduledExecutorService = scheduledExecutorService; this.log = log; diff --git a/common/src/main/java/org/apache/rocketmq/common/stats/RTStatsItem.java b/common/src/main/java/org/apache/rocketmq/common/stats/RTStatsItem.java index 102148cdc2e..b3317cf0b49 100644 --- a/common/src/main/java/org/apache/rocketmq/common/stats/RTStatsItem.java +++ b/common/src/main/java/org/apache/rocketmq/common/stats/RTStatsItem.java @@ -17,17 +17,17 @@ package org.apache.rocketmq.common.stats; -import org.apache.rocketmq.logging.InternalLogger; - 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, InternalLogger log) { - super(statsName, statsKey, scheduledExecutorService, log); + public RTStatsItem(String statsName, String statsKey, ScheduledExecutorService scheduledExecutorService, + Logger logger) { + super(statsName, statsKey, scheduledExecutorService, logger); } /** diff --git a/common/src/main/java/org/apache/rocketmq/common/stats/Stats.java b/common/src/main/java/org/apache/rocketmq/common/stats/Stats.java new file mode 100644 index 00000000000..b70f96e412e --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/stats/Stats.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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"; +} diff --git a/common/src/main/java/org/apache/rocketmq/common/stats/StatsItem.java b/common/src/main/java/org/apache/rocketmq/common/stats/StatsItem.java index d016662afad..8307c20aa68 100644 --- a/common/src/main/java/org/apache/rocketmq/common/stats/StatsItem.java +++ b/common/src/main/java/org/apache/rocketmq/common/stats/StatsItem.java @@ -23,31 +23,30 @@ import java.util.concurrent.atomic.LongAdder; import org.apache.rocketmq.common.UtilAll; -import org.apache.rocketmq.logging.InternalLogger; +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 csListMinute = new LinkedList<>(); - private final LinkedList csListHour = new LinkedList(); + private final LinkedList csListHour = new LinkedList<>(); - private final LinkedList csListDay = new LinkedList(); + private final LinkedList csListDay = new LinkedList<>(); private final String statsName; private final String statsKey; private final ScheduledExecutorService scheduledExecutorService; - private final InternalLogger log; - public StatsItem(String statsName, String statsKey, ScheduledExecutorService scheduledExecutorService, - InternalLogger log) { + private final Logger logger; + + public StatsItem(String statsName, String statsKey, ScheduledExecutorService scheduledExecutorService, Logger logger) { this.statsName = statsName; this.statsKey = statsKey; this.scheduledExecutorService = scheduledExecutorService; - this.log = log; + this.logger = logger; } private static StatsSnapshot computeStatsData(final LinkedList csList) { @@ -194,18 +193,18 @@ public void samplingInHour() { public void printAtMinutes() { StatsSnapshot ss = computeStatsData(this.csListMinute); - log.info(String.format("[%s] [%s] Stats In One Minute, ", this.statsName, this.statsKey) + statPrintDetail(ss)); + logger.info(String.format("[%s] [%s] Stats In One Minute, ", this.statsName, this.statsKey) + statPrintDetail(ss)); } public void printAtHour() { StatsSnapshot ss = computeStatsData(this.csListHour); - log.info(String.format("[%s] [%s] Stats In One Hour, ", this.statsName, this.statsKey) + statPrintDetail(ss)); + logger.info(String.format("[%s] [%s] Stats In One Hour, ", this.statsName, this.statsKey) + statPrintDetail(ss)); } public void printAtDay() { StatsSnapshot ss = computeStatsData(this.csListDay); - log.info(String.format("[%s] [%s] Stats In One Day, ", this.statsName, this.statsKey) + statPrintDetail(ss)); + logger.info(String.format("[%s] [%s] Stats In One Day, ", this.statsName, this.statsKey) + statPrintDetail(ss)); } protected String statPrintDetail(StatsSnapshot ss) { diff --git a/common/src/main/java/org/apache/rocketmq/common/stats/StatsItemSet.java b/common/src/main/java/org/apache/rocketmq/common/stats/StatsItemSet.java index 8d5418ef6ce..c5b140b5cc1 100644 --- a/common/src/main/java/org/apache/rocketmq/common/stats/StatsItemSet.java +++ b/common/src/main/java/org/apache/rocketmq/common/stats/StatsItemSet.java @@ -24,20 +24,21 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.rocketmq.common.UtilAll; -import org.apache.rocketmq.logging.InternalLogger; +import org.apache.rocketmq.logging.org.slf4j.Logger; public class StatsItemSet { private final ConcurrentMap statsItemTable = - new ConcurrentHashMap(128); + new ConcurrentHashMap<>(128); private final String statsName; private final ScheduledExecutorService scheduledExecutorService; - private final InternalLogger log; - public StatsItemSet(String statsName, ScheduledExecutorService scheduledExecutorService, InternalLogger log) { + private final Logger logger; + + public StatsItemSet(String statsName, ScheduledExecutorService scheduledExecutorService, Logger logger) { + this.logger = logger; this.statsName = statsName; this.scheduledExecutorService = scheduledExecutorService; - this.log = log; this.init(); } @@ -213,9 +214,9 @@ 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, this.log); + statsItem = new RTStatsItem(this.statsName, statsKey, this.scheduledExecutorService, logger); } else { - statsItem = new StatsItem(this.statsName, statsKey, this.scheduledExecutorService, this.log); + statsItem = new StatsItem(this.statsName, statsKey, this.scheduledExecutorService, logger); } StatsItem prev = this.statsItemTable.putIfAbsent(statsKey, statsItem); diff --git a/common/src/main/java/org/apache/rocketmq/common/sysflag/MessageSysFlag.java b/common/src/main/java/org/apache/rocketmq/common/sysflag/MessageSysFlag.java index d534571eb55..bf60602145d 100644 --- a/common/src/main/java/org/apache/rocketmq/common/sysflag/MessageSysFlag.java +++ b/common/src/main/java/org/apache/rocketmq/common/sysflag/MessageSysFlag.java @@ -16,7 +16,20 @@ */ 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; @@ -25,6 +38,15 @@ public class MessageSysFlag { 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; @@ -38,4 +60,13 @@ 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; + } + } diff --git a/common/src/main/java/org/apache/rocketmq/common/sysflag/PullSysFlag.java b/common/src/main/java/org/apache/rocketmq/common/sysflag/PullSysFlag.java index ce7558f2bcf..15d56dde78e 100644 --- a/common/src/main/java/org/apache/rocketmq/common/sysflag/PullSysFlag.java +++ b/common/src/main/java/org/apache/rocketmq/common/sysflag/PullSysFlag.java @@ -69,10 +69,18 @@ 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; } diff --git a/common/src/main/java/org/apache/rocketmq/common/thread/FutureTaskExtThreadPoolExecutor.java b/common/src/main/java/org/apache/rocketmq/common/thread/FutureTaskExtThreadPoolExecutor.java new file mode 100644 index 00000000000..411da922192 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/thread/FutureTaskExtThreadPoolExecutor.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolMonitor.java b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolMonitor.java new file mode 100644 index 00000000000..49d97a5d723 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolMonitor.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Executors; +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.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 = Executors.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 = new FutureTaskExtThreadPoolExecutor( + 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()); + waterMarkLogger.info("\t{}\t{}\t{}", threadPoolWrapper.getName(), + monitor.describe(), + 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(); + } +} \ No newline at end of file diff --git a/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolQueueSizeMonitor.java b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolQueueSizeMonitor.java new file mode 100644 index 00000000000..9e2e2f675ca --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolQueueSizeMonitor.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolStatusMonitor.java b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolStatusMonitor.java new file mode 100644 index 00000000000..548fec52ec0 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolStatusMonitor.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} \ No newline at end of file diff --git a/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolWrapper.java b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolWrapper.java new file mode 100644 index 00000000000..e41859dbf54 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolWrapper.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java b/common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java index c0525fa1585..61265c05d7c 100644 --- a/common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java +++ b/common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java @@ -16,12 +16,9 @@ */ package org.apache.rocketmq.common.topic; -import org.apache.rocketmq.common.UtilAll; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.remoting.protocol.RemotingCommand; - import java.util.HashSet; import java.util.Set; +import org.apache.rocketmq.common.UtilAll; public class TopicValidator { @@ -36,15 +33,17 @@ public class TopicValidator { public static final String RMQ_SYS_OFFSET_MOVED_EVENT = "OFFSET_MOVED_EVENT"; 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; - private static final Set SYSTEM_TOPIC_SET = new HashSet(); + private static final Set SYSTEM_TOPIC_SET = new HashSet<>(); /** * Topics'set which client can not send msg! */ - private static final Set NOT_ALLOWED_SEND_TOPIC_SET = new HashSet(); + private static final Set NOT_ALLOWED_SEND_TOPIC_SET = new HashSet<>(); static { SYSTEM_TOPIC_SET.add(AUTO_CREATE_TOPIC_KEY_TOPIC); @@ -58,6 +57,11 @@ public class TopicValidator { SYSTEM_TOPIC_SET.add(RMQ_SYS_OFFSET_MOVED_EVENT); 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); // regex: ^[%|a-zA-Z0-9_-]+$ // % @@ -95,36 +99,39 @@ public static boolean isTopicOrGroupIllegal(String str) { return false; } - public static boolean validateTopic(String topic, RemotingCommand response) { + public static ValidateTopicResult validateTopic(String topic) { if (UtilAll.isBlank(topic)) { - response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark("The specified topic is blank."); - return false; + return new ValidateTopicResult(false, "The specified topic is blank."); } if (isTopicOrGroupIllegal(topic)) { - response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark("The specified topic contains illegal characters, allowing only ^[%|a-zA-Z0-9_-]+$"); - return false; + return new ValidateTopicResult(false, "The specified topic contains illegal characters, allowing only ^[%|a-zA-Z0-9_-]+$"); } if (topic.length() > TOPIC_MAX_LENGTH) { - response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark("The specified topic is longer than topic max length."); - return false; + return new ValidateTopicResult(false, "The specified topic is longer than topic max length."); } - return true; + return new ValidateTopicResult(true, ""); } - public static boolean isSystemTopic(String topic, RemotingCommand response) { - if (isSystemTopic(topic)) { - response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark("The topic[" + topic + "] is conflict with system topic."); - return true; + public static class ValidateTopicResult { + private final boolean valid; + private final String remark; + + public ValidateTopicResult(boolean valid, String remark) { + this.valid = valid; + this.remark = remark; + } + + public boolean isValid() { + return valid; + } + + public String getRemark() { + return remark; } - return false; } public static boolean isSystemTopic(String topic) { @@ -135,15 +142,6 @@ public static boolean isNotAllowedSendTopic(String topic) { return NOT_ALLOWED_SEND_TOPIC_SET.contains(topic); } - public static boolean isNotAllowedSendTopic(String topic, RemotingCommand response) { - if (isNotAllowedSendTopic(topic)) { - response.setCode(ResponseCode.NO_PERMISSION); - response.setRemark("Sending message to topic[" + topic + "] is forbidden."); - return true; - } - return false; - } - public static void addSystemTopic(String systemTopic) { SYSTEM_TOPIC_SET.add(systemTopic); } diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/AbstractStartAndShutdown.java b/common/src/main/java/org/apache/rocketmq/common/utils/AbstractStartAndShutdown.java new file mode 100644 index 00000000000..78147b69032 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/AbstractStartAndShutdown.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 { + + } + }); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/BinaryUtil.java b/common/src/main/java/org/apache/rocketmq/common/utils/BinaryUtil.java new file mode 100644 index 00000000000..421adaca4da --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/BinaryUtil.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} \ No newline at end of file diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/CheckpointFile.java b/common/src/main/java/org/apache/rocketmq/common/utils/CheckpointFile.java new file mode 100644 index 00000000000..1cb85d05eee --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/CheckpointFile.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/CleanupPolicyUtils.java b/common/src/main/java/org/apache/rocketmq/common/utils/CleanupPolicyUtils.java new file mode 100644 index 00000000000..b73ece4f2c1 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/CleanupPolicyUtils.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/ConcurrentHashMapUtils.java b/common/src/main/java/org/apache/rocketmq/common/utils/ConcurrentHashMapUtils.java new file mode 100644 index 00000000000..1f1b4dd8907 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/ConcurrentHashMapUtils.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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. + * + * @see https://bugs.openjdk.java.net/browse/JDK-8161372 + */ + public static V computeIfAbsent(ConcurrentMap map, K key, Function func) { + if (isJdk8) { + V v = map.get(key); + if (null == v) { + v = map.computeIfAbsent(key, func); + } + return v; + } else { + return map.computeIfAbsent(key, func); + } + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java b/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java new file mode 100644 index 00000000000..8b50de12be8 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 Charset charset = 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/FastJsonSerializer.java b/common/src/main/java/org/apache/rocketmq/common/utils/FastJsonSerializer.java new file mode 100644 index 00000000000..600054b40b8 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/FastJsonSerializer.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.JSON; +import com.alibaba.fastjson.support.config.FastJsonConfig; +import org.apache.commons.lang3.SerializationException; + +/** + * The object serializer based on fastJson + */ +public class FastJsonSerializer implements Serializer { + private FastJsonConfig fastJsonConfig = new FastJsonConfig(); + + public FastJsonConfig getFastJsonConfig() { + return this.fastJsonConfig; + } + + public void setFastJsonConfig(FastJsonConfig fastJsonConfig) { + this.fastJsonConfig = fastJsonConfig; + } + + @Override + public byte[] serialize(T t) throws SerializationException { + if (t == null) { + return new byte[0]; + } else { + try { + return JSON.toJSONBytes(this.fastJsonConfig.getCharset(), t, this.fastJsonConfig.getSerializeConfig(), this.fastJsonConfig.getSerializeFilters(), this.fastJsonConfig.getDateFormat(), JSON.DEFAULT_GENERATE_FEATURE, this.fastJsonConfig.getSerializerFeatures()); + } 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, this.fastJsonConfig.getCharset(), type, this.fastJsonConfig.getParserConfig(), this.fastJsonConfig.getParseProcess(), JSON.DEFAULT_PARSER_FEATURE, this.fastJsonConfig.getFeatures()); + } catch (Exception var3) { + throw new SerializationException("Could not deserialize: " + var3.getMessage(), var3); + } + } else { + return null; + } + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/IOTinyUtils.java b/common/src/main/java/org/apache/rocketmq/common/utils/IOTinyUtils.java index 5ed82ae6e67..acba540d3a5 100644 --- a/common/src/main/java/org/apache/rocketmq/common/utils/IOTinyUtils.java +++ b/common/src/main/java/org/apache/rocketmq/common/utils/IOTinyUtils.java @@ -29,14 +29,14 @@ 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; -import org.apache.rocketmq.remoting.common.RemotingHelper; public class IOTinyUtils { static public String toString(InputStream input, String encoding) throws IOException { - return (null == encoding) ? toString(new InputStreamReader(input, RemotingHelper.DEFAULT_CHARSET)) : toString(new InputStreamReader( + return (null == encoding) ? toString(new InputStreamReader(input, StandardCharsets.UTF_8)) : toString(new InputStreamReader( input, encoding)); } @@ -58,7 +58,7 @@ static public long copy(Reader input, Writer output) throws IOException { static public List readLines(Reader input) throws IOException { BufferedReader reader = toBufferedReader(input); - List list = new ArrayList(); + List list = new ArrayList<>(); String line; for (; ; ) { line = reader.readLine(); diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/MessageUtils.java b/common/src/main/java/org/apache/rocketmq/common/utils/MessageUtils.java new file mode 100644 index 00000000000..4d6a150adce --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/MessageUtils.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingUtil.java b/common/src/main/java/org/apache/rocketmq/common/utils/NetworkUtil.java similarity index 70% rename from remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingUtil.java rename to common/src/main/java/org/apache/rocketmq/common/utils/NetworkUtil.java index d5ce20b0f12..7dd83e61799 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingUtil.java +++ b/common/src/main/java/org/apache/rocketmq/common/utils/NetworkUtil.java @@ -14,11 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.remoting.common; +package org.apache.rocketmq.common.utils; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; @@ -28,18 +25,17 @@ import java.net.NetworkInterface; import java.net.SocketAddress; import java.nio.channels.Selector; -import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; import java.util.ArrayList; import java.util.Enumeration; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.remoting.netty.NettySystemConfig; +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 RemotingUtil { +public class NetworkUtil { public static final String OS_NAME = System.getProperty("os.name"); - private static final InternalLogger log = InternalLoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING); + private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private static boolean isLinuxPlatform = false; private static boolean isWindowsPlatform = false; @@ -96,15 +92,15 @@ public static String getLocalAddress() { try { // Traversal Network interface to get the first non-loopback and non-private address Enumeration enumeration = NetworkInterface.getNetworkInterfaces(); - ArrayList ipv4Result = new ArrayList(); - ArrayList ipv6Result = new ArrayList(); + ArrayList ipv4Result = new ArrayList<>(); + ArrayList ipv6Result = new ArrayList<>(); while (enumeration.hasMoreElements()) { - final NetworkInterface networkInterface = enumeration.nextElement(); - if (isBridge(networkInterface)) { + final NetworkInterface nif = enumeration.nextElement(); + if (isBridge(nif) || nif.isVirtual() || nif.isPointToPoint() || !nif.isUp()) { continue; } - final Enumeration en = networkInterface.getInetAddresses(); + final Enumeration en = nif.getInetAddresses(); while (en.hasMoreElements()) { final InetAddress address = en.nextElement(); if (!address.isLoopbackAddress()) { @@ -120,7 +116,7 @@ public static String getLocalAddress() { // prefer ipv4 if (!ipv4Result.isEmpty()) { for (String ip : ipv4Result) { - if (ip.startsWith("127.0") || ip.startsWith("192.168")) { + if (ip.startsWith("127.0") || ip.startsWith("192.168") || ip.startsWith("0.")) { continue; } @@ -182,49 +178,4 @@ private static boolean isBridge(NetworkInterface networkInterface) { } return false; } - - 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); - 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()); - } - }); - } - } diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/PositiveAtomicCounter.java b/common/src/main/java/org/apache/rocketmq/common/utils/PositiveAtomicCounter.java new file mode 100644 index 00000000000..105b88c5770 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/PositiveAtomicCounter.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/QueueTypeUtils.java b/common/src/main/java/org/apache/rocketmq/common/utils/QueueTypeUtils.java new file mode 100644 index 00000000000..e2f006e12d2 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/QueueTypeUtils.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/Serializer.java b/common/src/main/java/org/apache/rocketmq/common/utils/Serializer.java new file mode 100644 index 00000000000..a98d2454d44 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/Serializer.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/util/ServiceProvider.java b/common/src/main/java/org/apache/rocketmq/common/utils/ServiceProvider.java similarity index 52% rename from broker/src/main/java/org/apache/rocketmq/broker/util/ServiceProvider.java rename to common/src/main/java/org/apache/rocketmq/common/utils/ServiceProvider.java index e679660104d..65dea47b5ea 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/util/ServiceProvider.java +++ b/common/src/main/java/org/apache/rocketmq/common/utils/ServiceProvider.java @@ -1,16 +1,27 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to - * You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - *

    - * http://www.apache.org/licenses/LICENSE-2.0 - *

    - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.slf4j.Logger; -import org.slf4j.LoggerFactory; +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; @@ -19,37 +30,27 @@ import java.util.List; public class ServiceProvider { - - private final static Logger LOG = LoggerFactory - .getLogger(ServiceProvider.class); + 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. + * JDK1.3+ 'Service Provider' + * specification. */ - public static final String TRANSACTION_SERVICE_ID = "META-INF/service/org.apache.rocketmq.broker.transaction.TransactionalMessageService"; - - public static final String TRANSACTION_LISTENER_ID = "META-INF/service/org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener"; - - - public static final String RPC_HOOK_ID = "META-INF/service/org.apache.rocketmq.remoting.RPCHook"; - - - public static final String ACL_VALIDATOR_ID = "META-INF/service/org.apache.rocketmq.acl.AccessValidator"; - - - + 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 overidden the toString method. + * 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 overidden the toString method. * * @param o may be null. * @return a string of form classname@hashcode, or "null" if param o is null. @@ -61,17 +62,17 @@ protected static String objectId(Object o) { 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 !", + 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 { @@ -85,7 +86,7 @@ protected static ClassLoader getContextClassLoader() { } return classLoader; } - + protected static InputStream getResourceAsStream(ClassLoader loader, String name) { if (loader != null) { return loader.getResourceAsStream(name); @@ -93,67 +94,63 @@ protected static InputStream getResourceAsStream(ClassLoader loader, String name 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(); - try { - ArrayList names = new ArrayList(); - final InputStream is = getResourceAsStream(getContextClassLoader(), name); - if (is != null) { - BufferedReader reader; - try { - reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); - } catch (java.io.UnsupportedEncodingException e) { - reader = new BufferedReader(new InputStreamReader(is)); - } - String serviceName = reader.readLine(); - 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((T)initService(getContextClassLoader(), serviceName, clazz)); - - serviceName = reader.readLine(); + 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)); } - reader.close(); - } else { - // is == null - LOG.warn("No resource file with name [{}] found.", name); + serviceName = reader.readLine(); } } catch (Exception e) { - LOG.error("Error occured when looking for resource file " + name, 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) { - final InputStream is = getResourceAsStream(getContextClassLoader(), name); - if (is != null) { - BufferedReader reader; - try { - try { - reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); - } catch (java.io.UnsupportedEncodingException e) { - reader = new BufferedReader(new InputStreamReader(is)); - } - String serviceName = reader.readLine(); - reader.close(); - if (serviceName != null && !"".equals(serviceName)) { - return initService(getContextClassLoader(), serviceName, clazz); - } else { - LOG.warn("ServiceName is empty!"); - return null; - } - } catch (Exception e) { - LOG.warn("Error occurred when looking for resource file " + name, e); + 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 null; + return s; } protected static T initService(ClassLoader classLoader, String serviceName, Class clazz) { @@ -170,14 +167,14 @@ protected static T initService(ClassLoader classLoader, String serviceName, // 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.", - new Object[] {serviceClazz.getName(), - objectId(serviceClazz.getClassLoader()), clazz.getName()}); + serviceClazz.getName(), + objectId(serviceClazz.getClassLoader()), clazz.getName()); } - return (T)serviceClazz.newInstance(); + 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, + LOG.warn("Unable to locate any class {} via classloader {}", serviceName, objectId(classLoader)); throw ex; } @@ -196,6 +193,6 @@ protected static T initService(ClassLoader classLoader, String serviceName, } catch (Exception e) { LOG.error("Unable to init service.", e); } - return (T)serviceClazz; + return (T) serviceClazz; } } diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/Shutdown.java b/common/src/main/java/org/apache/rocketmq/common/utils/Shutdown.java new file mode 100644 index 00000000000..07dc5f6767b --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/Shutdown.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; +} diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/Start.java b/common/src/main/java/org/apache/rocketmq/common/utils/Start.java new file mode 100644 index 00000000000..1e700dd70bc --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/Start.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; +} diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/StartAndShutdown.java b/common/src/main/java/org/apache/rocketmq/common/utils/StartAndShutdown.java new file mode 100644 index 00000000000..28999385a77 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/StartAndShutdown.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 {} +} diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/ThreadUtils.java b/common/src/main/java/org/apache/rocketmq/common/utils/ThreadUtils.java index 13c029396bd..4b366d4e39b 100644 --- a/common/src/main/java/org/apache/rocketmq/common/utils/ThreadUtils.java +++ b/common/src/main/java/org/apache/rocketmq/common/utils/ThreadUtils.java @@ -24,13 +24,13 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public final class ThreadUtils { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.TOOLS_LOGGER_NAME); + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.TOOLS_LOGGER_NAME); public static ExecutorService newThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, String processName, boolean isDaemon) { @@ -63,38 +63,20 @@ public static ThreadFactory newGenericThreadFactory(String processName, int thre } public static ThreadFactory newGenericThreadFactory(final String processName, final boolean isDaemon) { - return new ThreadFactory() { - private AtomicInteger threadIndex = new AtomicInteger(0); - - @Override - public Thread newThread(Runnable r) { - Thread thread = new Thread(r, String.format("%s_%d", processName, this.threadIndex.incrementAndGet())); - thread.setDaemon(isDaemon); - return thread; - } - }; + return new ThreadFactoryImpl(processName + "_", isDaemon); } public static ThreadFactory newGenericThreadFactory(final String processName, final int threads, final boolean isDaemon) { - return new ThreadFactory() { - private AtomicInteger threadIndex = new AtomicInteger(0); - - @Override - public Thread newThread(Runnable r) { - Thread thread = new Thread(r, String.format("%s_%d_%d", processName, threads, this.threadIndex.incrementAndGet())); - thread.setDaemon(isDaemon); - return thread; - } - }; + return new ThreadFactoryImpl(String.format("%s_%d_", processName, threads), isDaemon); } /** * Create a new thread * - * @param name The name of the 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? + * @param daemon Should the thread block JVM stop? * @return The unstarted thread */ public static Thread newThread(String name, Runnable runnable, boolean daemon) { @@ -102,7 +84,7 @@ public static Thread newThread(String name, Runnable runnable, boolean daemon) { thread.setDaemon(daemon); thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { - log.error("Uncaught exception in thread '" + t.getName() + "':", e); + LOGGER.error("Uncaught exception in thread '" + t.getName() + "':", e); } }); return thread; @@ -121,7 +103,7 @@ public static void shutdownGracefully(final Thread t) { * Shutdown passed thread using isAlive and join. * * @param millis Pass 0 if we're to wait forever. - * @param t Thread to stop + * @param t Thread to stop */ public static void shutdownGracefully(final Thread t, final long millis) { if (t == null) @@ -141,7 +123,7 @@ public static void shutdownGracefully(final Thread t, final long millis) { * {@link ExecutorService}. * * @param executor executor - * @param timeout timeout + * @param timeout timeout * @param timeUnit timeUnit */ public static void shutdownGracefully(ExecutorService executor, long timeout, TimeUnit timeUnit) { @@ -153,7 +135,7 @@ public static void shutdownGracefully(ExecutorService executor, long timeout, Ti executor.shutdownNow(); // Wait a while for tasks to respond to being cancelled. if (!executor.awaitTermination(timeout, timeUnit)) { - log.warn(String.format("%s didn't terminate!", executor)); + LOGGER.warn(String.format("%s didn't terminate!", executor)); } } } catch (InterruptedException ie) { @@ -164,6 +146,17 @@ public static void shutdownGracefully(ExecutorService executor, long timeout, Ti } } + /** + * 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. */ diff --git a/common/src/main/resources/META-INF/services/org.apache.rocketmq.logging.ch.qos.logback.classic.spi.Configurator b/common/src/main/resources/META-INF/services/org.apache.rocketmq.logging.ch.qos.logback.classic.spi.Configurator new file mode 100644 index 00000000000..b90cd30997f --- /dev/null +++ b/common/src/main/resources/META-INF/services/org.apache.rocketmq.logging.ch.qos.logback.classic.spi.Configurator @@ -0,0 +1 @@ +org.apache.rocketmq.common.logging.DefaultJoranConfiguratorExt \ No newline at end of file diff --git a/common/src/test/java/org/apache/rocketmq/common/ConfigManagerTest.java b/common/src/test/java/org/apache/rocketmq/common/ConfigManagerTest.java index a884b6a00bf..a61ec4c0525 100644 --- a/common/src/test/java/org/apache/rocketmq/common/ConfigManagerTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/ConfigManagerTest.java @@ -15,13 +15,10 @@ * limitations under the License. */ -import org.apache.rocketmq.common.ConfigManager; -import org.apache.rocketmq.common.MixAll; -import org.junit.Test; - 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; diff --git a/common/src/test/java/org/apache/rocketmq/common/MessageBatchTest.java b/common/src/test/java/org/apache/rocketmq/common/MessageBatchTest.java index f264420760b..5876cbdd881 100644 --- a/common/src/test/java/org/apache/rocketmq/common/MessageBatchTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/MessageBatchTest.java @@ -26,7 +26,7 @@ public class MessageBatchTest { public List generateMessages() { - List messages = new ArrayList(); + List messages = new ArrayList<>(); Message message1 = new Message("topic1", "body".getBytes()); Message message2 = new Message("topic1", "body".getBytes()); diff --git a/common/src/test/java/org/apache/rocketmq/common/MessageEncodeDecodeTest.java b/common/src/test/java/org/apache/rocketmq/common/MessageEncodeDecodeTest.java index 42d3909d99e..7c73147e0c7 100644 --- a/common/src/test/java/org/apache/rocketmq/common/MessageEncodeDecodeTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/MessageEncodeDecodeTest.java @@ -46,7 +46,7 @@ public void testEncodeDecodeSingle() throws Exception { @Test public void testEncodeDecodeList() throws Exception { - List messages = new ArrayList(128); + List messages = new ArrayList<>(128); for (int i = 0; i < 100; i++) { Message message = new Message("topic", ("body" + i).getBytes()); message.setFlag(i); diff --git a/common/src/test/java/org/apache/rocketmq/common/MixAllTest.java b/common/src/test/java/org/apache/rocketmq/common/MixAllTest.java index 4f2a341553e..efb42085f95 100644 --- a/common/src/test/java/org/apache/rocketmq/common/MixAllTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/MixAllTest.java @@ -67,22 +67,6 @@ public void testFile2String() throws IOException { file.delete(); } - @Test - public void testFile2String_WithChinese() 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(); diff --git a/common/src/test/java/org/apache/rocketmq/common/RemotingUtilTest.java b/common/src/test/java/org/apache/rocketmq/common/NetworkUtilTest.java similarity index 73% rename from common/src/test/java/org/apache/rocketmq/common/RemotingUtilTest.java rename to common/src/test/java/org/apache/rocketmq/common/NetworkUtilTest.java index 19346e6bca2..aa4d355f834 100644 --- a/common/src/test/java/org/apache/rocketmq/common/RemotingUtilTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/NetworkUtilTest.java @@ -16,28 +16,30 @@ */ package org.apache.rocketmq.common; -import org.apache.rocketmq.remoting.common.RemotingUtil; +import java.net.InetAddress; +import org.apache.rocketmq.common.utils.NetworkUtil; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -public class RemotingUtilTest { +public class NetworkUtilTest { @Test - public void testGetLocalAddress() throws Exception { - String localAddress = RemotingUtil.getLocalAddress(); + public void testGetLocalAddress() { + String localAddress = NetworkUtil.getLocalAddress(); assertThat(localAddress).isNotNull(); assertThat(localAddress.length()).isGreaterThan(0); + assertThat(localAddress).isNotEqualTo(InetAddress.getLoopbackAddress().getHostAddress()); } @Test public void testConvert2IpStringWithIp() { - String result = RemotingUtil.convert2IpString("127.0.0.1:9876"); + String result = NetworkUtil.convert2IpString("127.0.0.1:9876"); assertThat(result).isEqualTo("127.0.0.1:9876"); } @Test public void testConvert2IpStringWithHost() { - String result = RemotingUtil.convert2IpString("localhost:9876"); + String result = NetworkUtil.convert2IpString("localhost:9876"); assertThat(result).isEqualTo("127.0.0.1:9876"); } } diff --git a/common/src/test/java/org/apache/rocketmq/common/ServiceThreadTest.java b/common/src/test/java/org/apache/rocketmq/common/ServiceThreadTest.java index 24a4af89bc1..93208bcb7f2 100644 --- a/common/src/test/java/org/apache/rocketmq/common/ServiceThreadTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/ServiceThreadTest.java @@ -31,12 +31,6 @@ public void testShutdown() { shutdown(true, true); } - @Test - public void testStop() { - stop(true); - stop(false); - } - @Test public void testMakeStop() { ServiceThread testServiceThread = startTestServiceThread(); @@ -116,23 +110,4 @@ private void shutdown0(boolean interrupt, ServiceThread testServiceThread) { assertEquals(true, testServiceThread.hasNotified.get()); assertEquals(0, testServiceThread.waitPoint.getCount()); } - - public void stop(boolean interrupt) { - ServiceThread testServiceThread = startTestServiceThread(); - stop0(interrupt, testServiceThread); - // repeat - stop0(interrupt, testServiceThread); - } - - private void stop0(boolean interrupt, ServiceThread testServiceThread) { - if (interrupt) { - testServiceThread.stop(true); - } else { - testServiceThread.stop(); - } - assertEquals(true, testServiceThread.isStopped()); - assertEquals(true, testServiceThread.hasNotified.get()); - assertEquals(0, testServiceThread.waitPoint.getCount()); - } - } diff --git a/common/src/test/java/org/apache/rocketmq/common/TopicConfigTest.java b/common/src/test/java/org/apache/rocketmq/common/TopicConfigTest.java new file mode 100644 index 00000000000..3df93a0bfb3 --- /dev/null +++ b/common/src/test/java/org/apache/rocketmq/common/TopicConfigTest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} \ No newline at end of file diff --git a/common/src/test/java/org/apache/rocketmq/common/UtilAllTest.java b/common/src/test/java/org/apache/rocketmq/common/UtilAllTest.java index ffbcd33e4c1..f568a65f4d5 100644 --- a/common/src/test/java/org/apache/rocketmq/common/UtilAllTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/UtilAllTest.java @@ -17,8 +17,11 @@ 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; @@ -28,6 +31,7 @@ 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 { @@ -55,16 +59,26 @@ public void testProperties2Object() { @Test public void testProperties2String() { - DemoConfig demoConfig = new DemoConfig(); + 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 @@ -119,7 +133,8 @@ public void testJoin() { String comma = ","; assertEquals("groupA=DENY,groupB=PUB|SUB,groupC=SUB", UtilAll.join(list, comma)); assertEquals(null, UtilAll.join(null, comma)); - assertEquals("", UtilAll.join(Collections.emptyList(), comma)); + List objects = Collections.emptyList(); + assertEquals("", UtilAll.join(objects, comma)); } static class DemoConfig { @@ -170,4 +185,100 @@ public String toString() { '}'; } } + + 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.allocate(10)); + UtilAll.cleanBuffer(ByteBuffer.allocate(0)); + } + + @Test(expected = NoSuchMethodException.class) + public void testMethod() throws NoSuchMethodException { + UtilAll.method(new Object(), "noMethod", null); + } + + @Test(expected = IllegalStateException.class) + public void testInvoke() throws Exception { + UtilAll.invoke(new Object(), "noMethod"); + } + + @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 + */ + String basePath = System.getProperty("java.io.tmpdir") + File.separator + "testCalculateFileSizeInPath"; + File baseFile = new File(basePath); + // test empty path + assertEquals(0, UtilAll.calculateFileSizeInPath(baseFile)); + + // create baseDir + assertTrue(baseFile.mkdirs()); + + 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)); + + // clear all file + baseFile.deleteOnExit(); + } + + 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(); + } } diff --git a/common/src/test/java/org/apache/rocketmq/common/attribute/AttributeParserTest.java b/common/src/test/java/org/apache/rocketmq/common/attribute/AttributeParserTest.java new file mode 100644 index 00000000000..12398100bec --- /dev/null +++ b/common/src/test/java/org/apache/rocketmq/common/attribute/AttributeParserTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.google.common.collect.Maps.newHashMap; +import static org.junit.Assert.assertTrue; + +public class AttributeParserTest { + @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); + } +} diff --git a/common/src/test/java/org/apache/rocketmq/common/attribute/AttributeTest.java b/common/src/test/java/org/apache/rocketmq/common/attribute/AttributeTest.java new file mode 100644 index 00000000000..39a12b97ef4 --- /dev/null +++ b/common/src/test/java/org/apache/rocketmq/common/attribute/AttributeTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Test; + +import static com.google.common.collect.Sets.newHashSet; + +public class AttributeTest { + + @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"); + } +} diff --git a/common/src/test/java/org/apache/rocketmq/common/compression/CompressionTest.java b/common/src/test/java/org/apache/rocketmq/common/compression/CompressionTest.java new file mode 100644 index 00000000000..8110cfb6e38 --- /dev/null +++ b/common/src/test/java/org/apache/rocketmq/common/compression/CompressionTest.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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"); + } +} diff --git a/common/src/test/java/org/apache/rocketmq/common/message/MessageClientIDSetterTest.java b/common/src/test/java/org/apache/rocketmq/common/message/MessageClientIDSetterTest.java index 1734cbdf755..22e6f3c27a1 100644 --- a/common/src/test/java/org/apache/rocketmq/common/message/MessageClientIDSetterTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/message/MessageClientIDSetterTest.java @@ -22,8 +22,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.nio.charset.StandardCharsets; - public class MessageClientIDSetterTest { @Test @@ -31,7 +29,7 @@ public void testGetTimeFromID() { long t = System.currentTimeMillis(); String uniqID = MessageClientIDSetter.createUniqID(); long t2 = MessageClientIDSetter.getNearlyTimeFromID(uniqID).getTime(); - assertThat(t2 - t < 20); + assertThat(t2 - t < 20).isTrue(); } @Test @@ -42,7 +40,7 @@ public void testGetCountFromID() { String idHex2 = uniqID2.substring(uniqID2.length() - 4); int s1 = Integer.parseInt(idHex, 16); int s2 = Integer.parseInt(idHex2, 16); - assertThat(s1 == s2 - 1); + assertThat(s1 == s2 - 1).isTrue(); } diff --git a/common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java b/common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java index b27f24669aa..39bfbf5fb3f 100644 --- a/common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java @@ -17,6 +17,8 @@ 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; @@ -28,6 +30,7 @@ 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 { @@ -62,23 +65,45 @@ public void testDecodeProperties() { 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(); + { + 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")); } - ByteBuffer byteBuffer = ByteBuffer.allocate(msgBytes.length); - byteBuffer.put(msgBytes); + { + byte[] msgBytes = new byte[0]; + try { + msgBytes = MessageDecoder.encode(messageExt, false); + } catch (Exception e) { + e.printStackTrace(); + assertThat(Boolean.FALSE).isTrue(); + } - Map properties = MessageDecoder.decodeProperties(byteBuffer); + ByteBuffer byteBuffer = ByteBuffer.allocate(msgBytes.length); + byteBuffer.put(msgBytes); - assertThat(properties).isNotNull(); - assertThat("123").isEqualTo(properties.get("a")); - assertThat("hello").isEqualTo(properties.get("b")); - assertThat("3.14").isEqualTo(properties.get("c")); + 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 @@ -162,6 +187,8 @@ public void testEncodeAndDecode() { 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); @@ -173,7 +200,7 @@ public void testEncodeAndDecode() { ByteBuffer byteBuffer = ByteBuffer.allocate(msgBytes.length); byteBuffer.put(msgBytes); - byteBuffer.clear(); + byteBuffer.flip(); MessageExt decodedMsg = MessageDecoder.decode(byteBuffer); assertThat(decodedMsg).isNotNull(); @@ -221,6 +248,8 @@ public void testEncodeAndDecodeOnIPv6Host() { 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); @@ -232,14 +261,15 @@ public void testEncodeAndDecodeOnIPv6Host() { ByteBuffer byteBuffer = ByteBuffer.allocate(msgBytes.length); byteBuffer.put(msgBytes); - byteBuffer.clear(); + 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(48).isEqualTo(decodedMsg.getSysFlag()); + assertThat(MessageSysFlag.check(messageExt.getSysFlag(), MessageSysFlag.STOREHOSTADDRESS_V6_FLAG)).isTrue(); int msgIDLength = 16 + 4 + 8; ByteBuffer byteBufferMsgId = ByteBuffer.allocate(msgIDLength); @@ -249,6 +279,7 @@ public void testEncodeAndDecodeOnIPv6Host() { assertThat("abc").isEqualTo(decodedMsg.getTopic()); } + @Test public void testNullValueProperty() throws Exception { MessageExt msg = new MessageExt(); msg.setBody("x".getBytes()); @@ -373,4 +404,28 @@ public void testString2messageProperties() { 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()); + } } \ No newline at end of file diff --git a/common/src/test/java/org/apache/rocketmq/common/message/MessageTest.java b/common/src/test/java/org/apache/rocketmq/common/message/MessageTest.java index c867360f85b..c950970980b 100644 --- a/common/src/test/java/org/apache/rocketmq/common/message/MessageTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/message/MessageTest.java @@ -20,7 +20,6 @@ import org.junit.Test; import static org.apache.rocketmq.common.message.MessageConst.PROPERTY_TRACE_SWITCH; -import static org.junit.Assert.*; public class MessageTest { @Test(expected = RuntimeException.class) diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/topic/OffsetMovedEventTest.java b/common/src/test/java/org/apache/rocketmq/common/protocol/topic/OffsetMovedEventTest.java deleted file mode 100644 index c22c2d62a28..00000000000 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/topic/OffsetMovedEventTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF 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.protocol.topic; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; -import org.junit.Test; - -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; - } -} diff --git a/common/src/test/java/org/apache/rocketmq/common/stats/StatsItemSetTest.java b/common/src/test/java/org/apache/rocketmq/common/stats/StatsItemSetTest.java index d834160d63c..b02fb60ae49 100644 --- a/common/src/test/java/org/apache/rocketmq/common/stats/StatsItemSetTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/stats/StatsItemSetTest.java @@ -52,7 +52,7 @@ public void test_statsOfFirstStatisticsCycle() throws InterruptedException { 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")); + new ArrayBlockingQueue<>(100), new ThreadFactoryImpl("testMultiThread")); for (int i = 0; i < 10; i++) { executor.submit(new Runnable() { @Override @@ -100,7 +100,7 @@ public void run() { 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")); + new ArrayBlockingQueue<>(100), new ThreadFactoryImpl("testMultiThread")); for (int i = 0; i < 10; i++) { executor.submit(new Runnable() { @Override @@ -121,7 +121,7 @@ public void run() { 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")); + new ArrayBlockingQueue<>(100), new ThreadFactoryImpl("testMultiThread")); for (int i = 0; i < 10; i++) { executor.submit(new Runnable() { @Override diff --git a/common/src/test/java/org/apache/rocketmq/common/sysflag/CompressionFlagTest.java b/common/src/test/java/org/apache/rocketmq/common/sysflag/CompressionFlagTest.java new file mode 100644 index 00000000000..8590d569513 --- /dev/null +++ b/common/src/test/java/org/apache/rocketmq/common/sysflag/CompressionFlagTest.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/common/src/test/java/org/apache/rocketmq/common/topic/TopicValidatorTest.java b/common/src/test/java/org/apache/rocketmq/common/topic/TopicValidatorTest.java index bb49417b00c..65954fa932e 100644 --- a/common/src/test/java/org/apache/rocketmq/common/topic/TopicValidatorTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/topic/TopicValidatorTest.java @@ -16,9 +16,6 @@ */ package org.apache.rocketmq.common.topic; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.topic.TopicValidator; -import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -27,34 +24,24 @@ public class TopicValidatorTest { @Test public void testTopicValidator_NotPass() { - RemotingCommand response = RemotingCommand.createResponseCommand(-1, ""); + TopicValidator.ValidateTopicResult res = TopicValidator.validateTopic(""); + assertThat(res.isValid()).isFalse(); + assertThat(res.getRemark()).contains("The specified topic is blank"); - Boolean res = TopicValidator.validateTopic("", response); - assertThat(res).isFalse(); - assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); - assertThat(response.getRemark()).contains("The specified topic is blank"); - - clearResponse(response); - res = TopicValidator.validateTopic("../TopicTest", response); - assertThat(res).isFalse(); - assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); - assertThat(response.getRemark()).contains("The specified topic contains illegal characters"); + res = TopicValidator.validateTopic("../TopicTest"); + assertThat(res.isValid()).isFalse(); + assertThat(res.getRemark()).contains("The specified topic contains illegal characters"); - clearResponse(response); - res = TopicValidator.validateTopic(generateString(128), response); - assertThat(res).isFalse(); - assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); - assertThat(response.getRemark()).contains("The specified topic is longer than topic max length."); + res = TopicValidator.validateTopic(generateString(128)); + assertThat(res.isValid()).isFalse(); + assertThat(res.getRemark()).contains("The specified topic is longer than topic max length."); } @Test public void testTopicValidator_Pass() { - RemotingCommand response = RemotingCommand.createResponseCommand(-1, ""); - - Boolean res = TopicValidator.validateTopic("TestTopic", response); - assertThat(res).isTrue(); - assertThat(response.getCode()).isEqualTo(-1); - assertThat(response.getRemark()).isEmpty(); + TopicValidator.ValidateTopicResult res = TopicValidator.validateTopic("TestTopic"); + assertThat(res.isValid()).isTrue(); + assertThat(res.getRemark()).isEmpty(); } @Test @@ -83,17 +70,14 @@ public void testIsSystemTopic() { @Test public void testIsSystemTopicWithResponse() { - RemotingCommand response = RemotingCommand.createResponseCommand(-1, ""); boolean res; for (String topic : TopicValidator.getSystemTopicSet()) { - res = TopicValidator.isSystemTopic(topic, response); + res = TopicValidator.isSystemTopic(topic); assertThat(res).isTrue(); - assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR); - assertThat(response.getRemark()).isEqualTo("The topic[" + topic + "] is conflict with system topic."); } String topic = "test_not_system_topic"; - res = TopicValidator.isSystemTopic(topic, response); + res = TopicValidator.isSystemTopic(topic); assertThat(res).isFalse(); } @@ -112,26 +96,17 @@ public void testIsNotAllowedSendTopic() { @Test public void testIsNotAllowedSendTopicWithResponse() { - RemotingCommand response = RemotingCommand.createResponseCommand(-1, ""); - boolean res; for (String topic : TopicValidator.getNotAllowedSendTopicSet()) { - res = TopicValidator.isNotAllowedSendTopic(topic, response); + res = TopicValidator.isNotAllowedSendTopic(topic); assertThat(res).isTrue(); - assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION); - assertThat(response.getRemark()).isEqualTo("Sending message to topic[" + topic + "] is forbidden."); } String topic = "test_allowed_send_topic"; - res = TopicValidator.isNotAllowedSendTopic(topic, response); + res = TopicValidator.isNotAllowedSendTopic(topic); assertThat(res).isFalse(); } - private static void clearResponse(RemotingCommand response) { - response.setCode(-1); - response.setRemark(""); - } - private static String generateString(int length) { StringBuilder stringBuffer = new StringBuilder(); String tmpStr = "0123456789"; diff --git a/common/src/test/java/org/apache/rocketmq/common/utils/ConcurrentHashMapUtilsTest.java b/common/src/test/java/org/apache/rocketmq/common/utils/ConcurrentHashMapUtilsTest.java new file mode 100644 index 00000000000..8e32fc93aa7 --- /dev/null +++ b/common/src/test/java/org/apache/rocketmq/common/utils/ConcurrentHashMapUtilsTest.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} \ No newline at end of file diff --git a/common/src/test/java/org/apache/rocketmq/common/utils/IOTinyUtilsTest.java b/common/src/test/java/org/apache/rocketmq/common/utils/IOTinyUtilsTest.java index 6a63eecc223..732013179b2 100644 --- a/common/src/test/java/org/apache/rocketmq/common/utils/IOTinyUtilsTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/utils/IOTinyUtilsTest.java @@ -17,22 +17,33 @@ 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.apache.rocketmq.remoting.common.RemotingHelper; - -import static org.junit.Assert.*; - import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.io.*; -import java.lang.reflect.Method; -import java.util.List; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class IOTinyUtilsTest { - private String testRootDir = System.getProperty("user.home") + File.separator + "iotinyutilstest"; + /** + * https://bazel.build/reference/test-encyclopedia#filesystem + */ + private String testRootDir = System.getProperty("java.io.tmpdir") + File.separator + "iotinyutilstest"; @Before public void init() { @@ -45,26 +56,25 @@ public void init() { } @After - public void destory() { + public void destroy() { File file = new File(testRootDir); UtilAll.deleteFile(file); } - @Test public void testToString() throws Exception { - byte[] b = "testToString".getBytes(RemotingHelper.DEFAULT_CHARSET); + 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, RemotingHelper.DEFAULT_CHARSET); + str = IOTinyUtils.toString(is, StandardCharsets.UTF_8.name()); assertEquals("testToString", str); is = new ByteArrayInputStream(b); - Reader isr = new InputStreamReader(is, RemotingHelper.DEFAULT_CHARSET); + Reader isr = new InputStreamReader(is, StandardCharsets.UTF_8); str = IOTinyUtils.toString(isr); assertEquals("testToString", str); } @@ -113,7 +123,7 @@ public void testWriteStringToFile() throws Exception { File file = new File(testRootDir, "testWriteStringToFile"); assertTrue(!file.exists()); - IOTinyUtils.writeStringToFile(file, "testWriteStringToFile", RemotingHelper.DEFAULT_CHARSET); + IOTinyUtils.writeStringToFile(file, "testWriteStringToFile", StandardCharsets.UTF_8.name()); assertTrue(file.exists()); } @@ -121,7 +131,7 @@ public void testWriteStringToFile() throws Exception { @Test public void testCleanDirectory() throws Exception { for (int i = 0; i < 10; i++) { - IOTinyUtils.writeStringToFile(new File(testRootDir, "testCleanDirectory" + i), "testCleanDirectory", RemotingHelper.DEFAULT_CHARSET); + IOTinyUtils.writeStringToFile(new File(testRootDir, "testCleanDirectory" + i), "testCleanDirectory", StandardCharsets.UTF_8.name()); } File dir = new File(testRootDir); @@ -136,7 +146,7 @@ public void testCleanDirectory() throws Exception { @Test public void testDelete() throws Exception { for (int i = 0; i < 10; i++) { - IOTinyUtils.writeStringToFile(new File(testRootDir, "testDelete" + i), "testCleanDirectory", RemotingHelper.DEFAULT_CHARSET); + IOTinyUtils.writeStringToFile(new File(testRootDir, "testDelete" + i), "testCleanDirectory", StandardCharsets.UTF_8.name()); } File dir = new File(testRootDir); @@ -150,10 +160,10 @@ public void testDelete() throws Exception { @Test public void testCopyFile() throws Exception { - File source = new File(testRootDir, "soruce"); + File source = new File(testRootDir, "source"); String target = testRootDir + File.separator + "dest"; - IOTinyUtils.writeStringToFile(source, "testCopyFile", RemotingHelper.DEFAULT_CHARSET); + IOTinyUtils.writeStringToFile(source, "testCopyFile", StandardCharsets.UTF_8.name()); IOTinyUtils.copyFile(source.getCanonicalPath(), target); diff --git a/common/src/test/resources/rmq.logback-test.xml b/common/src/test/resources/rmq.logback-test.xml new file mode 100644 index 00000000000..8695d52d57c --- /dev/null +++ b/common/src/test/resources/rmq.logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/container/BUILD.bazel b/container/BUILD.bazel new file mode 100644 index 00000000000..059d7c2252c --- /dev/null +++ b/container/BUILD.bazel @@ -0,0 +1,82 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 = [ + "//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//:com_alibaba_fastjson", + "@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", + "//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", + "@maven//:com_alibaba_fastjson", + ], + 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", + ], +) diff --git a/logging/pom.xml b/container/pom.xml similarity index 73% rename from logging/pom.xml rename to container/pom.xml index fbc4b1e002b..6881bca5620 100644 --- a/logging/pom.xml +++ b/container/pom.xml @@ -14,30 +14,26 @@ See the License for the specific language governing permissions and limitations under the License. --> - org.apache.rocketmq rocketmq-all - 4.9.4-SNAPSHOT + 5.1.3 4.0.0 jar - rocketmq-logging - rocketmq-logging ${project.version} + rocketmq-container + rocketmq-container ${project.version} + + + ${basedir}/.. + - org.slf4j - slf4j-api - true - - - ch.qos.logback - logback-classic - test + org.apache.rocketmq + rocketmq-broker - - \ No newline at end of file + diff --git a/container/src/main/java/org/apache/rocketmq/container/BrokerBootHook.java b/container/src/main/java/org/apache/rocketmq/container/BrokerBootHook.java new file mode 100644 index 00000000000..fe126af3a27 --- /dev/null +++ b/container/src/main/java/org/apache/rocketmq/container/BrokerBootHook.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; +import org.apache.rocketmq.broker.BrokerController; + +public interface BrokerBootHook { + /** + * Name of the hook. + * + * @return name of the hook + */ + String hookName(); + + /** + * Code to execute before broker start. + * + * @param brokerController broker to start + * @param properties broker properties + * @throws Exception when execute hook + */ + void executeBeforeStart(BrokerController brokerController, Properties properties) throws Exception; + + /** + * Code to execute after broker start. + * + * @param brokerController broker to start + * @param properties broker properties + * @throws Exception when execute hook + */ + void executeAfterStart(BrokerController brokerController, Properties properties) throws Exception; +} diff --git a/container/src/main/java/org/apache/rocketmq/container/BrokerContainer.java b/container/src/main/java/org/apache/rocketmq/container/BrokerContainer.java new file mode 100644 index 00000000000..c6446f058fa --- /dev/null +++ b/container/src/main/java/org/apache/rocketmq/container/BrokerContainer.java @@ -0,0 +1,487 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.broker.BrokerController; +import org.apache.rocketmq.broker.BrokerPathConfigHelper; +import org.apache.rocketmq.broker.out.BrokerOuterAPI; +import org.apache.rocketmq.common.AbstractBrokerRunnable; +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.container.logback.BrokerLogbackConfigurator; +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.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +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 = new ScheduledThreadPoolExecutor(1, + new BasicThreadFactory.Builder() + .namingPattern("BrokerContainerScheduledThread") + .daemon(true) + .build()); + private final NettyServerConfig nettyServerConfig; + private final NettyClientConfig nettyClientConfig; + private final BrokerOuterAPI brokerOuterAPI; + private final ContainerClientHouseKeepingService containerClientHouseKeepingService; + + private final ConcurrentMap slaveBrokerControllers = new ConcurrentHashMap<>(); + private final ConcurrentMap masterBrokerControllers = new ConcurrentHashMap<>(); + private final ConcurrentMap dLedgerBrokerControllers = new ConcurrentHashMap<>(); + private final List brokerBootHookList = new ArrayList<>(); + private final BrokerContainerProcessor brokerContainerProcessor; + private final Configuration configuration; + private final BrokerContainerConfig brokerContainerConfig; + + private RemotingServer remotingServer; + private RemotingServer fastRemotingServer; + private 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); + + 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 = new ThreadPoolExecutor( + 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 AbstractBrokerRunnable(BrokerIdentity.BROKER_CONTAINER_IDENTITY) { + @Override + public void run0() { + try { + BrokerContainer.this.updateNamesrvAddr(); + } catch (Throwable e) { + LOG.error("ScheduledTask fetchNameServerAddr exception", e); + } + } + }, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS); + } else if (this.brokerContainerConfig.isFetchNamesrvAddrByAddressServer()) { + this.scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(BrokerIdentity.BROKER_CONTAINER_IDENTITY) { + + @Override + public void run0() { + 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 AbstractBrokerRunnable(BrokerIdentity.BROKER_CONTAINER_IDENTITY) { + @Override + public void run0() { + try { + BrokerContainer.this.brokerOuterAPI.refreshMetadata(); + } catch (Exception e) { + LOG.error("ScheduledTask refresh metadata exception", e); + } + } + }, 10, 5, TimeUnit.SECONDS); + + return true; + } + + private 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(final BrokerConfig brokerConfig, + final MessageStoreConfig storeConfig) throws Exception { + if (storeConfig.isEnableDLegerCommitLog()) { + return this.addDLedgerBroker(brokerConfig, storeConfig); + } else { + if (brokerConfig.getBrokerId() == MixAll.MASTER_ID && storeConfig.getBrokerRole() != BrokerRole.SLAVE) { + return this.addMasterBroker(brokerConfig, storeConfig); + } + if (brokerConfig.getBrokerId() != MixAll.MASTER_ID && storeConfig.getBrokerRole() == BrokerRole.SLAVE) { + return this.addSlaveBroker(brokerConfig, storeConfig); + } + } + + return null; + } + + public InnerBrokerController addDLedgerBroker(final BrokerConfig brokerConfig, final MessageStoreConfig storeConfig) 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); + BrokerIdentity brokerIdentity = brokerController.getBrokerIdentity(); + final BrokerController previousBroker = dLedgerBrokerControllers.putIfAbsent(brokerIdentity, brokerController); + if (previousBroker == null) { + // New dLedger broker added, start it + try { + BrokerLogbackConfigurator.doConfigure(brokerIdentity); + 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) 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); + BrokerIdentity brokerIdentity = masterBroker.getBrokerIdentity(); + final BrokerController previousBroker = masterBrokerControllers.putIfAbsent(brokerIdentity, masterBroker); + if (previousBroker == null) { + // New master broker added, start it + try { + BrokerLogbackConfigurator.doConfigure(masterBrokerConfig); + 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) 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); + BrokerIdentity brokerIdentity = slaveBroker.getBrokerIdentity(); + final InnerSalveBrokerController previousBroker = slaveBrokerControllers.putIfAbsent(brokerIdentity, slaveBroker); + if (previousBroker == null) { + // New slave broker added, start it + try { + BrokerLogbackConfigurator.doConfigure(slaveBrokerConfig); + 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; + } +} diff --git a/container/src/main/java/org/apache/rocketmq/container/BrokerContainerConfig.java b/container/src/main/java/org/apache/rocketmq/container/BrokerContainerConfig.java new file mode 100644 index 00000000000..77422adde8f --- /dev/null +++ b/container/src/main/java/org/apache/rocketmq/container/BrokerContainerConfig.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV)); + + @ImportantField + private String namesrvAddr = System.getProperty(MixAll.NAMESRV_ADDR_PROPERTY, System.getenv(MixAll.NAMESRV_ADDR_ENV)); + + @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; + + 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; + } +} diff --git a/container/src/main/java/org/apache/rocketmq/container/BrokerContainerProcessor.java b/container/src/main/java/org/apache/rocketmq/container/BrokerContainerProcessor.java new file mode 100644 index 00000000000..2ac69112d76 --- /dev/null +++ b/container/src/main/java/org/apache/rocketmq/container/BrokerContainerProcessor.java @@ -0,0 +1,282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.List; +import java.util.Properties; +import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.broker.BrokerStartup; +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 { + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + private final BrokerContainer brokerContainer; + private List brokerBootHookList; + + public BrokerContainerProcessor(BrokerContainer brokerContainer) { + this.brokerContainer = brokerContainer; + } + + @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; + } + + private 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 { + byte[] body = request.getBody(); + if (body != null) { + String bodyStr = new String(body, MixAll.DEFAULT_CHARSET); + brokerProperties = MixAll.string2Properties(bodyStr); + } + } + + 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(); + MixAll.properties2Object(brokerProperties, brokerConfig); + MixAll.properties2Object(brokerProperties, messageStoreConfig); + + messageStoreConfig.setHaListenPort(brokerConfig.getListenPort() + 1); + + if (configPath != null && !configPath.isEmpty()) { + 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; + } + } + + BrokerController brokerController; + try { + brokerController = this.brokerContainer.addBroker(brokerConfig, messageStoreConfig); + } catch (Exception e) { + LOGGER.error("addBroker exception {}", e); + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark(e.getMessage()); + return response; + } + if (brokerController != null) { + brokerController.getConfiguration().registerConfig(brokerProperties); + try { + for (BrokerBootHook brokerBootHook : brokerBootHookList) { + brokerBootHook.executeBeforeStart(brokerController, brokerProperties); + } + brokerController.start(); + + for (BrokerBootHook brokerBootHook : brokerBootHookList) { + brokerBootHook.executeAfterStart(brokerController, 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); + brokerController.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; + } + + private 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.info("updateSharedBrokerConfig, new config: [{}] client: {} ", properties, ctx.channel().remoteAddress()); + this.brokerContainer.getConfiguration().update(properties); + } else { + LOGGER.error("string2Properties error"); + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark("string2Properties error"); + return response; + } + } 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 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 { + 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; + } +} diff --git a/container/src/main/java/org/apache/rocketmq/container/BrokerContainerStartup.java b/container/src/main/java/org/apache/rocketmq/container/BrokerContainerStartup.java new file mode 100644 index 00000000000..f909e623b21 --- /dev/null +++ b/container/src/main/java/org/apache/rocketmq/container/BrokerContainerStartup.java @@ -0,0 +1,428 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.broker.BrokerController; +import org.apache.rocketmq.broker.BrokerPathConfigHelper; +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 BrokerContainer brokerContainer = startBrokerContainer(createBrokerContainer(args)); + createAndStartBrokers(brokerContainer); + } + + public static BrokerController createBrokerController(String[] args) { + final BrokerContainer brokerContainer = startBrokerContainer(createBrokerContainer(args)); + return createAndInitializeBroker(brokerContainer, configFile, properties); + } + + 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 BrokerController brokerController = createAndInitializeBroker(brokerContainer, configPath, brokerProperties); + if (brokerController != null) { + brokerControllerList.add(brokerController); + startBrokerController(brokerContainer, brokerController, 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 BrokerController createAndInitializeBroker(BrokerContainer brokerContainer, + String filePath, Properties brokerProperties) { + + final BrokerConfig brokerConfig = new BrokerConfig(); + final MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); + + if (brokerProperties != null) { + properties2SystemEnv(brokerProperties); + MixAll.properties2Object(brokerProperties, brokerConfig); + MixAll.properties2Object(brokerProperties, messageStoreConfig); + } + + 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); + + try { + BrokerController brokerController = brokerContainer.addBroker(brokerConfig, messageStoreConfig); + if (brokerController != null) { + brokerController.getConfiguration().registerConfig(brokerProperties); + return brokerController; + } 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, + BrokerController brokerController, Properties brokerProperties) { + try { + for (BrokerBootHook hook : brokerContainer.getBrokerBootHookList()) { + hook.executeBeforeStart(brokerController, brokerProperties); + } + + brokerController.start(); + + for (BrokerBootHook hook : brokerContainer.getBrokerBootHookList()) { + hook.executeAfterStart(brokerController, brokerProperties); + } + + String tip = String.format("Broker [%s-%s] boot success. serializeType=%s", + brokerController.getBrokerConfig().getBrokerName(), + brokerController.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 BrokerContainer createBrokerContainer(String[] args) { + 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); + } + + final BrokerContainerConfig containerConfig = new BrokerContainerConfig(); + final NettyServerConfig nettyServerConfig = new NettyServerConfig(); + final NettyClientConfig nettyClientConfig = new NettyClientConfig(); + 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); + } + + 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); + } + + 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")); + + return brokerContainer; + } 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(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 { + InputStream in = new BufferedInputStream(new FileInputStream(file)); + Properties properties = new Properties(); + properties.load(in); + in.close(); + 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; + } + } + +} diff --git a/container/src/main/java/org/apache/rocketmq/container/ContainerClientHouseKeepingService.java b/container/src/main/java/org/apache/rocketmq/container/ContainerClientHouseKeepingService.java new file mode 100644 index 00000000000..8bf4b4a33d0 --- /dev/null +++ b/container/src/main/java/org/apache/rocketmq/container/ContainerClientHouseKeepingService.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + + 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 + } +} diff --git a/container/src/main/java/org/apache/rocketmq/container/IBrokerContainer.java b/container/src/main/java/org/apache/rocketmq/container/IBrokerContainer.java new file mode 100644 index 00000000000..d3cdc05b87b --- /dev/null +++ b/container/src/main/java/org/apache/rocketmq/container/IBrokerContainer.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.out.BrokerOuterAPI; +import org.apache.rocketmq.common.BrokerConfig; +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; +import org.apache.rocketmq.store.config.MessageStoreConfig; + +/** + * 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 brokerConfig the specified broker config + * @param storeConfig the specified store config + * @return the added BrokerController or null if the broker already exists + * @throws Exception when initialize broker + */ + BrokerController addBroker(BrokerConfig brokerConfig, MessageStoreConfig storeConfig) 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(); +} diff --git a/container/src/main/java/org/apache/rocketmq/container/InnerBrokerController.java b/container/src/main/java/org/apache/rocketmq/container/InnerBrokerController.java new file mode 100644 index 00000000000..a1c1eecf590 --- /dev/null +++ b/container/src/main/java/org/apache/rocketmq/container/InnerBrokerController.java @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.broker.BrokerController; +import org.apache.rocketmq.broker.out.BrokerOuterAPI; +import org.apache.rocketmq.common.AbstractBrokerRunnable; +import org.apache.rocketmq.common.BrokerConfig; +import org.apache.rocketmq.common.MixAll; +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; + +public class InnerBrokerController extends BrokerController { + protected BrokerContainer brokerContainer; + + public InnerBrokerController( + final BrokerContainer brokerContainer, + final BrokerConfig brokerConfig, + final MessageStoreConfig messageStoreConfig + ) { + super(brokerConfig, messageStoreConfig); + this.brokerContainer = brokerContainer; + this.brokerOuterAPI = this.brokerContainer.getBrokerOuterAPI(); + } + + @Override + protected void initializeRemotingServer() { + this.remotingServer = this.brokerContainer.getRemotingServer().newRemotingServer(brokerConfig.getListenPort()); + this.fastRemotingServer = this.brokerContainer.getRemotingServer().newRemotingServer(brokerConfig.getListenPort() - 2); + } + + @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 AbstractBrokerRunnable(this.getBrokerIdentity()) { + @Override + public void run0() { + 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 AbstractBrokerRunnable(this.getBrokerIdentity()) { + @Override + public void run0() { + 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 (this.remotingServer != null) { + this.brokerContainer.getRemotingServer().removeRemotingServer(brokerConfig.getListenPort()); + } + + if (this.fastRemotingServer != 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(); + } +} diff --git a/container/src/main/java/org/apache/rocketmq/container/InnerSalveBrokerController.java b/container/src/main/java/org/apache/rocketmq/container/InnerSalveBrokerController.java new file mode 100644 index 00000000000..a7901bc7dc7 --- /dev/null +++ b/container/src/main/java/org/apache/rocketmq/container/InnerSalveBrokerController.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.rocketmq.common.BrokerConfig; +import org.apache.rocketmq.common.MixAll; +import org.apache.rocketmq.store.config.MessageStoreConfig; + +public class InnerSalveBrokerController extends InnerBrokerController { + + private final Lock lock = new ReentrantLock(); + + public InnerSalveBrokerController(final BrokerContainer brokerContainer, + final BrokerConfig brokerConfig, + final MessageStoreConfig storeConfig) { + super(brokerContainer, brokerConfig, storeConfig); + // Check configs + checkSlaveBrokerConfig(); + } + + private void checkSlaveBrokerConfig() { + Preconditions.checkNotNull(brokerConfig.getBrokerClusterName()); + Preconditions.checkNotNull(brokerConfig.getBrokerName()); + Preconditions.checkArgument(brokerConfig.getBrokerId() != MixAll.MASTER_ID); + } +} diff --git a/container/src/main/java/org/apache/rocketmq/container/logback/BrokerLogbackConfigurator.java b/container/src/main/java/org/apache/rocketmq/container/logback/BrokerLogbackConfigurator.java new file mode 100644 index 00000000000..bf51819542d --- /dev/null +++ b/container/src/main/java/org/apache/rocketmq/container/logback/BrokerLogbackConfigurator.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.logback; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.rocketmq.common.BrokerIdentity; +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 BrokerLogbackConfigurator { + private static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + + private static final Set CONFIGURED_BROKER_LIST = new HashSet<>(); + + public static final String ROCKETMQ_LOGS = "rocketmqlogs"; + public static final String ROCKETMQ_PREFIX = "Rocketmq"; + public static final String SUFFIX_CONSOLE = "Console"; + public static final String SUFFIX_APPENDER = "Appender"; + public static final String SUFFIX_INNER_APPENDER = "_inner"; + + public static void doConfigure(BrokerIdentity brokerIdentity) { + } +} diff --git a/container/src/test/java/org/apache/rocketmq/container/BrokerContainerStartupTest.java b/container/src/test/java/org/apache/rocketmq/container/BrokerContainerStartupTest.java new file mode 100644 index 00000000000..1b9ef6d0d27 --- /dev/null +++ b/container/src/test/java/org/apache/rocketmq/container/BrokerContainerStartupTest.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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() { + BrokerContainer brokerContainer = BrokerContainerStartup.startBrokerContainer( + BrokerContainerStartup.createBrokerContainer(Arrays.array("-c", brokerContainerConfigPath))); + 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()); + } +} \ No newline at end of file diff --git a/container/src/test/java/org/apache/rocketmq/container/BrokerContainerTest.java b/container/src/test/java/org/apache/rocketmq/container/BrokerContainerTest.java new file mode 100644 index 00000000000..e02d9ac3b88 --- /dev/null +++ b/container/src/test/java/org/apache/rocketmq/container/BrokerContainerTest.java @@ -0,0 +1,379 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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"); + InnerBrokerController brokerController = brokerContainer.addBroker(masterBrokerConfig, messageStoreConfig); + 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"); + brokerContainer.addBroker(masterBrokerConfig, messageStoreConfig); + } 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; + try { + sharedBrokerController.addBroker(slaveBrokerConfig, slaveMessageStoreConfig); + } 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"); + InnerBrokerController master = brokerContainer.addBroker(masterBrokerConfig, messageStoreConfig); + 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))); + InnerBrokerController dLedger = brokerContainer.addBroker(dLedgerBrokerConfig, messageStoreConfig); + 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"); + InnerBrokerController master = brokerContainer.addBroker(masterBrokerConfig, messageStoreConfig); + 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"); + InnerBrokerController slave = brokerContainer.addBroker(slaveBrokerConfig, slaveMessageStoreConfig); + 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); + } + } +} diff --git a/container/src/test/java/org/apache/rocketmq/container/BrokerPreOnlineTest.java b/container/src/test/java/org/apache/rocketmq/container/BrokerPreOnlineTest.java new file mode 100644 index 00000000000..2158b8d9aca --- /dev/null +++ b/container/src/test/java/org/apache/rocketmq/container/BrokerPreOnlineTest.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.lang.reflect.Field; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +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.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 static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class BrokerPreOnlineTest { + @Mock + private BrokerContainer brokerContainer; + + private InnerBrokerController innerBrokerController; + + @Mock + private BrokerOuterAPI brokerOuterAPI; + + public void init() throws Exception { + when(brokerContainer.getBrokerOuterAPI()).thenReturn(brokerOuterAPI); + + BrokerMemberGroup brokerMemberGroup1 = new BrokerMemberGroup(); + Map brokerAddrMap = new HashMap<>(); + brokerAddrMap.put(1L, "127.0.0.1:20911"); + brokerMemberGroup1.setBrokerAddrs(brokerAddrMap); + + BrokerMemberGroup brokerMemberGroup2 = new BrokerMemberGroup(); + brokerMemberGroup2.setBrokerAddrs(new HashMap<>()); + +// when(brokerOuterAPI.syncBrokerMemberGroup(anyString(), anyString())) +// .thenReturn(brokerMemberGroup1) +// .thenReturn(brokerMemberGroup2); +// doNothing().when(brokerOuterAPI).sendBrokerHaInfo(anyString(), anyString(), anyLong(), anyString()); + + DefaultMessageStore defaultMessageStore = mock(DefaultMessageStore.class); + when(defaultMessageStore.getMessageStoreConfig()).thenReturn(new MessageStoreConfig()); + when(defaultMessageStore.getBrokerConfig()).thenReturn(new BrokerConfig()); + +// HAService haService = new DefaultHAService(); +// haService.init(defaultMessageStore); +// haService.start(); +// +// when(defaultMessageStore.getHaService()).thenReturn(haService); + + innerBrokerController = new InnerBrokerController(brokerContainer, + defaultMessageStore.getBrokerConfig(), + defaultMessageStore.getMessageStoreConfig()); + + 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(); + BrokerPreOnlineService brokerPreOnlineService = new BrokerPreOnlineService(innerBrokerController); + + brokerPreOnlineService.start(); + + await().atMost(Duration.ofSeconds(30)).until(() -> !innerBrokerController.isIsolated()); + } +} diff --git a/container/src/test/resources/rmq.logback-test.xml b/container/src/test/resources/rmq.logback-test.xml new file mode 100644 index 00000000000..8695d52d57c --- /dev/null +++ b/container/src/test/resources/rmq.logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/controller/BUILD.bazel b/controller/BUILD.bazel new file mode 100644 index 00000000000..843d9dc7766 --- /dev/null +++ b/controller/BUILD.bazel @@ -0,0 +1,89 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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", + "//client", + "//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_fastjson", + "@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//:org_slf4j_jul_to_slf4j", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":controller", + "//common", + "//remoting", + "//client", + "//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_fastjson", + ], + 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", + ], +) diff --git a/controller/pom.xml b/controller/pom.xml new file mode 100644 index 00000000000..beb0a05834a --- /dev/null +++ b/controller/pom.xml @@ -0,0 +1,66 @@ + + + + + rocketmq-all + org.apache.rocketmq + 5.1.3 + + 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 + + + \ No newline at end of file diff --git a/controller/src/main/java/org/apache/rocketmq/controller/BrokerHeartbeatManager.java b/controller/src/main/java/org/apache/rocketmq/controller/BrokerHeartbeatManager.java new file mode 100644 index 00000000000..f38a03a7b4a --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/BrokerHeartbeatManager.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.Map; +import org.apache.rocketmq.controller.helper.BrokerLifecycleListener; +import org.apache.rocketmq.controller.impl.heartbeat.BrokerLiveInfo; + +public interface BrokerHeartbeatManager { + + /** + * initialize the resources + * @return + */ + 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 + */ + 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(); +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/BrokerHousekeepingService.java b/controller/src/main/java/org/apache/rocketmq/controller/BrokerHousekeepingService.java new file mode 100644 index 00000000000..652a9eeb0d6 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/BrokerHousekeepingService.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/Controller.java b/controller/src/main/java/org/apache/rocketmq/controller/Controller.java new file mode 100644 index 00000000000..cda613091e3 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/Controller.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/ControllerManager.java b/controller/src/main/java/org/apache/rocketmq/controller/ControllerManager.java new file mode 100644 index 00000000000..7c91e70da50 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/ControllerManager.java @@ -0,0 +1,353 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.RunnableFuture; +import java.util.concurrent.ThreadPoolExecutor; +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.future.FutureTaskExt; + +import org.apache.rocketmq.controller.elect.impl.DefaultElectPolicy; +import org.apache.rocketmq.controller.impl.DLedgerController; +import org.apache.rocketmq.controller.impl.heartbeat.DefaultBrokerHeartbeatManager; +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 BrokerHeartbeatManager heartbeatManager; + private ExecutorService controllerRequestExecutor; + private BlockingQueue controllerRequestThreadPoolQueue; + + private 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 = new DefaultBrokerHeartbeatManager(this.controllerConfig); + this.notifyService = new NotifyService(); + } + + public boolean initialize() { + this.controllerRequestThreadPoolQueue = new LinkedBlockingQueue<>(this.controllerConfig.getControllerRequestThreadPoolQueueCapacity()); + this.controllerRequestExecutor = new ThreadPoolExecutor( + this.controllerConfig.getControllerThreadPoolNums(), + this.controllerConfig.getControllerThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + this.controllerRequestThreadPoolQueue, + new ThreadFactoryImpl("ControllerRequestExecutorThread_")) { + @Override + protected RunnableFuture newTaskFor(final Runnable runnable, final T value) { + return new FutureTaskExt(runnable, value); + } + }; + this.notifyService.initialize(); + 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) { + 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 void triggerElectMaster(String brokerName) { + final CompletableFuture electMasterFuture = controller.electMaster(ElectMasterRequestHeader.ofControllerTrigger(brokerName)); + electMasterFuture.whenCompleteAsync((electMasterResponse, err) -> { + if (err != null || electMasterResponse == null) { + log.error("Failed to trigger elect-master in broker-set: {}", brokerName, err); + return; + } + 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)); + } + } + }); + } + + /** + * 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); + final 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.heartbeatManager.start(); + this.controller.startup(); + 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()); + } + } + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/ControllerStartup.java b/controller/src/main/java/org/apache/rocketmq/controller/ControllerStartup.java new file mode 100644 index 00000000000..401720d0507 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/ControllerStartup.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 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, nettyServerConfig); + MixAll.properties2Object(properties, nettyClientConfig); + + System.out.printf("load config properties file OK, %s%n", file); + in.close(); + } + } + + if (commandLine.hasOption('p')) { + MixAll.printObjectProperties(null, controllerConfig); + MixAll.printObjectProperties(null, nettyServerConfig); + MixAll.printObjectProperties(null, 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; + } + +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/elect/ElectPolicy.java b/controller/src/main/java/org/apache/rocketmq/controller/elect/ElectPolicy.java new file mode 100644 index 00000000000..8e4e75a2c53 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/elect/ElectPolicy.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/elect/impl/DefaultElectPolicy.java b/controller/src/main/java/org/apache/rocketmq/controller/elect/impl/DefaultElectPolicy.java new file mode 100644 index 00000000000..283a281affb --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/elect/impl/DefaultElectPolicy.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/helper/BrokerLifecycleListener.java b/controller/src/main/java/org/apache/rocketmq/controller/helper/BrokerLifecycleListener.java new file mode 100644 index 00000000000..31fa47632b7 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/helper/BrokerLifecycleListener.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/helper/BrokerLiveInfoGetter.java b/controller/src/main/java/org/apache/rocketmq/controller/helper/BrokerLiveInfoGetter.java new file mode 100644 index 00000000000..afdb2700ac2 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/helper/BrokerLiveInfoGetter.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/helper/BrokerValidPredicate.java b/controller/src/main/java/org/apache/rocketmq/controller/helper/BrokerValidPredicate.java new file mode 100644 index 00000000000..d8c6a2f6580 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/helper/BrokerValidPredicate.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/DLedgerController.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/DLedgerController.java new file mode 100644 index 00000000000..fa91f288e2d --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/DLedgerController.java @@ -0,0 +1,600 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Executors; +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.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 List brokerLifecycleListeners; + + // Usr for checking whether the broker is alive + private BrokerValidPredicate brokerAlivePredicate; + // use for elect a master + private ElectPolicy electPolicy; + + private 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 = Executors.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 = Executors.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(String.format("Controller leader append initial log failed, try %d 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; + } + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/DLedgerControllerStateMachine.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/DLedgerControllerStateMachine.java new file mode 100644 index 00000000000..8dc7e5dfa76 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/DLedgerControllerStateMachine.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.snapshot.SnapshotReader; +import io.openmessaging.storage.dledger.snapshot.SnapshotWriter; +import io.openmessaging.storage.dledger.statemachine.CommittedEntryIterator; +import io.openmessaging.storage.dledger.statemachine.StateMachine; +import java.util.concurrent.CompletableFuture; +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 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 void onSnapshotSave(SnapshotWriter writer, CompletableFuture future) { + } + + @Override + public boolean onSnapshotLoad(SnapshotReader reader) { + return false; + } + + @Override + public void onShutdown() { + } + + @Override + public String getBindDLedgerId() { + return this.dLedgerId; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/event/AlterSyncStateSetEvent.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/AlterSyncStateSetEvent.java new file mode 100644 index 00000000000..6af44b7229c --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/AlterSyncStateSetEvent.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + + '}'; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/event/ApplyBrokerIdEvent.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/ApplyBrokerIdEvent.java new file mode 100644 index 00000000000..a0bf001c46f --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/ApplyBrokerIdEvent.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + + '}'; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/event/CleanBrokerDataEvent.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/CleanBrokerDataEvent.java new file mode 100644 index 00000000000..e639e27e4db --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/CleanBrokerDataEvent.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + + '}'; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/event/ControllerResult.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/ControllerResult.java new file mode 100644 index 00000000000..d661d73e125 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/ControllerResult.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + + '}'; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/event/ElectMasterEvent.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/ElectMasterEvent.java new file mode 100644 index 00000000000..71a56bdce68 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/ElectMasterEvent.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + + '}'; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/event/EventMessage.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/EventMessage.java new file mode 100644 index 00000000000..8a31393e1cd --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/EventMessage.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/event/EventSerializer.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/EventSerializer.java new file mode 100644 index 00000000000..b5358c7c3ed --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/EventSerializer.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/event/EventType.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/EventType.java new file mode 100644 index 00000000000..2b4cefb1d73 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/EventType.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/event/UpdateBrokerAddressEvent.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/UpdateBrokerAddressEvent.java new file mode 100644 index 00000000000..d40121ee0bb --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/event/UpdateBrokerAddressEvent.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/BrokerIdentityInfo.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/BrokerIdentityInfo.java new file mode 100644 index 00000000000..44449d0d048 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/BrokerIdentityInfo.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.Objects; + +public class BrokerIdentityInfo { + 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 clusterName.isEmpty() && brokerName.isEmpty() && 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 + + '}'; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/BrokerLiveInfo.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/BrokerLiveInfo.java new file mode 100644 index 00000000000..3ab7975f380 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/BrokerLiveInfo.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +public class BrokerLiveInfo { + 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; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/DefaultBrokerHeartbeatManager.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/DefaultBrokerHeartbeatManager.java new file mode 100644 index 00000000000..2fbddb9cdf9 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/DefaultBrokerHeartbeatManager.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 static final long DEFAULT_BROKER_CHANNEL_EXPIRED_TIME = 1000 * 10; + 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 = Executors.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 ? 0 : num + 1 + ); + }); + return map; + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/BrokerReplicaInfo.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/BrokerReplicaInfo.java new file mode 100644 index 00000000000..f930747938c --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/BrokerReplicaInfo.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; +import org.apache.rocketmq.common.MixAll; +import org.apache.rocketmq.common.Pair; + +/** + * Broker replicas info, mapping from brokerAddress to {brokerId, brokerHaAddress}. + */ +public class BrokerReplicaInfo { + 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())); + } + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManager.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManager.java new file mode 100644 index 00000000000..b0a67531da2 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManager.java @@ -0,0 +1,579 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; + +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.admin.CleanControllerBrokerDataRequestHeader; +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; + + +/** + * 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); + private final ControllerConfig controllerConfig; + private final Map replicaInfoTable; + private final Map syncStateSetInfoTable; + + 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().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) { + // 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); + 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) { + 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"); + } + 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); + } + +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/SyncStateInfo.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/SyncStateInfo.java new file mode 100644 index 00000000000..a01298d9af8 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/SyncStateInfo.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 { + 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); + } +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsConstant.java b/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsConstant.java new file mode 100644 index 00000000000..1efae43fe90 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsConstant.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } + } + +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsManager.java b/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsManager.java new file mode 100644 index 00000000000..9b30a3b43b9 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsManager.java @@ -0,0 +1,383 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.LoggingMetricExporter; +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.PeriodicMetricReader; +import io.opentelemetry.sdk.resources.Resource; +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 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 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 LoggingMetricExporter 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(); + 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 latecyView = 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, latecyView); + providerBuilder.registerView(dLedgerOpLatencySelector, latecyView); + } + + 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 = LoggingMetricExporter.create(config.isMetricsInDelta() ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE); + java.util.logging.Logger.getLogger(LoggingMetricExporter.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); + } + +} diff --git a/controller/src/main/java/org/apache/rocketmq/controller/processor/ControllerRequestProcessor.java b/controller/src/main/java/org/apache/rocketmq/controller/processor/ControllerRequestProcessor.java new file mode 100644 index 00000000000..93ecbbd9dd2 --- /dev/null +++ b/controller/src/main/java/org/apache/rocketmq/controller/processor/ControllerRequestProcessor.java @@ -0,0 +1,323 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.List; +import java.util.Properties; +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.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 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 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; + + public ControllerRequestProcessor(final ControllerManager controllerManager) { + this.controllerManager = controllerManager; + this.heartbeatManager = controllerManager.getHeartbeatManager(); + } + + @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 (properties.containsKey("configStorePath")) { + response.setCode(ResponseCode.NO_PERMISSION); + response.setRemark("Can not update config path"); + 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 { + 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; + } + +} diff --git a/controller/src/main/resources/rmq.controller.logback.xml b/controller/src/main/resources/rmq.controller.logback.xml new file mode 100644 index 00000000000..bb158213af6 --- /dev/null +++ b/controller/src/main/resources/rmq.controller.logback.xml @@ -0,0 +1,142 @@ + + + + + + ${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}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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/controller/src/test/java/org/apache/rocketmq/controller/ControllerManagerTest.java b/controller/src/test/java/org/apache/rocketmq/controller/ControllerManagerTest.java new file mode 100644 index 00000000000..8ad67d404e5 --- /dev/null +++ b/controller/src/test/java/org/apache/rocketmq/controller/ControllerManagerTest.java @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 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.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.controller.GetReplicaInfoRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoResponseHeader; +import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; + +import org.junit.After; +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.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(); + } +} diff --git a/controller/src/test/java/org/apache/rocketmq/controller/ControllerRequestProcessorTest.java b/controller/src/test/java/org/apache/rocketmq/controller/ControllerRequestProcessorTest.java new file mode 100644 index 00000000000..ede6ca36a49 --- /dev/null +++ b/controller/src/test/java/org/apache/rocketmq/controller/ControllerRequestProcessorTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.nio.charset.StandardCharsets; +import java.util.Properties; +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 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 path"); + + } +} diff --git a/controller/src/test/java/org/apache/rocketmq/controller/ControllerTestBase.java b/controller/src/test/java/org/apache/rocketmq/controller/ControllerTestBase.java new file mode 100644 index 00000000000..f77f49dcf25 --- /dev/null +++ b/controller/src/test/java/org/apache/rocketmq/controller/ControllerTestBase.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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"}; +} diff --git a/controller/src/test/java/org/apache/rocketmq/controller/impl/DLedgerControllerTest.java b/controller/src/test/java/org/apache/rocketmq/controller/impl/DLedgerControllerTest.java new file mode 100644 index 00000000000..595a5cb6536 --- /dev/null +++ b/controller/src/test/java/org/apache/rocketmq/controller/impl/DLedgerControllerTest.java @@ -0,0 +1,386 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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 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 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); + // 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; + try { + leader.electMaster(request).get(5, TimeUnit.SECONDS); + } catch (Exception e) { + exception = e; + } + assertNotNull(exception); + // 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); + } +} diff --git a/controller/src/test/java/org/apache/rocketmq/controller/impl/DefaultBrokerHeartbeatManagerTest.java b/controller/src/test/java/org/apache/rocketmq/controller/impl/DefaultBrokerHeartbeatManagerTest.java new file mode 100644 index 00000000000..395f3bab4fc --- /dev/null +++ b/controller/src/test/java/org/apache/rocketmq/controller/impl/DefaultBrokerHeartbeatManagerTest.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +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 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(); + } + +} \ No newline at end of file diff --git a/controller/src/test/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManagerTest.java b/controller/src/test/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManagerTest.java new file mode 100644 index 00000000000..e2c32b03b7f --- /dev/null +++ b/controller/src/test/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManagerTest.java @@ -0,0 +1,466 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +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.heartbeat.DefaultBrokerHeartbeatManager; +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.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.admin.CleanControllerBrokerDataRequestHeader; +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.junit.After; +import org.junit.Before; +import org.junit.Test; + +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()); + + } +} diff --git a/controller/src/test/resources/rmq.logback-test.xml b/controller/src/test/resources/rmq.logback-test.xml new file mode 100644 index 00000000000..8695d52d57c --- /dev/null +++ b/controller/src/test/resources/rmq.logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/dev/merge_rocketmq_pr.py b/dev/merge_rocketmq_pr.py index 849b0da2afe..981b2c22313 100644 --- a/dev/merge_rocketmq_pr.py +++ b/dev/merge_rocketmq_pr.py @@ -17,8 +17,8 @@ # limitations under the License. # -# This script is a modified version of the one created by the Spark -# project (https://github.com/apache/spark/blob/master/dev/merge_spark_pr.py). +# 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) @@ -448,4 +448,4 @@ def main(): main() except: clean_up() - raise \ No newline at end of file + raise diff --git a/distribution/benchmark/runclass.sh b/distribution/benchmark/runclass.sh index 565ad0275eb..885e2227513 100644 --- a/distribution/benchmark/runclass.sh +++ b/distribution/benchmark/runclass.sh @@ -60,6 +60,8 @@ 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 diff --git a/distribution/benchmark/shutdown.sh b/distribution/benchmark/shutdown.sh index 9ecd32600e3..2af7ef741d9 100644 --- a/distribution/benchmark/shutdown.sh +++ b/distribution/benchmark/shutdown.sh @@ -24,7 +24,7 @@ case $1 in exit -1; fi - echo "The benchmkar producer(${pid}) is running..." + echo "The benchmark producer(${pid}) is running..." kill ${pid} @@ -52,12 +52,26 @@ case $1 in exit -1; fi - echo "The benchmkar transaction producer(${pid}) is running..." + 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 "Useage: shutdown producer | consumer | tproducer" + echo "Usage: shutdown producer | consumer | tproducer | bproducer" esac diff --git a/distribution/bin/controller/fast-try-independent-deployment.cmd b/distribution/bin/controller/fast-try-independent-deployment.cmd new file mode 100644 index 00000000000..debddef767a --- /dev/null +++ b/distribution/bin/controller/fast-try-independent-deployment.cmd @@ -0,0 +1,36 @@ +@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" +) diff --git a/distribution/bin/controller/fast-try-independent-deployment.sh b/distribution/bin/controller/fast-try-independent-deployment.sh new file mode 100644 index 00000000000..7aa52d51900 --- /dev/null +++ b/distribution/bin/controller/fast-try-independent-deployment.sh @@ -0,0 +1,76 @@ +#!/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 + diff --git a/distribution/bin/controller/fast-try-namesrv-plugin.cmd b/distribution/bin/controller/fast-try-namesrv-plugin.cmd new file mode 100644 index 00000000000..6633d3ac4b0 --- /dev/null +++ b/distribution/bin/controller/fast-try-namesrv-plugin.cmd @@ -0,0 +1,36 @@ +@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" +) \ No newline at end of file diff --git a/distribution/bin/controller/fast-try-namesrv-plugin.sh b/distribution/bin/controller/fast-try-namesrv-plugin.sh new file mode 100644 index 00000000000..a349153cc11 --- /dev/null +++ b/distribution/bin/controller/fast-try-namesrv-plugin.sh @@ -0,0 +1,76 @@ +#!/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 + diff --git a/distribution/bin/controller/fast-try.cmd b/distribution/bin/controller/fast-try.cmd new file mode 100644 index 00000000000..a32ed61ad3c --- /dev/null +++ b/distribution/bin/controller/fast-try.cmd @@ -0,0 +1,37 @@ +@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" +) \ No newline at end of file diff --git a/distribution/bin/controller/fast-try.sh b/distribution/bin/controller/fast-try.sh new file mode 100644 index 00000000000..211a4ff8a47 --- /dev/null +++ b/distribution/bin/controller/fast-try.sh @@ -0,0 +1,101 @@ +#!/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 + diff --git a/distribution/bin/mqadmin b/distribution/bin/mqadmin index 980d6e3b6bd..489da8b7659 100644 --- a/distribution/bin/mqadmin +++ b/distribution/bin/mqadmin @@ -42,4 +42,4 @@ fi export ROCKETMQ_HOME -sh ${ROCKETMQ_HOME}/bin/tools.sh org.apache.rocketmq.tools.command.MQAdminStartup "$@" +sh ${ROCKETMQ_HOME}/bin/tools.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.tools.logback.xml org.apache.rocketmq.tools.command.MQAdminStartup "$@" diff --git a/distribution/bin/mqadmin.cmd b/distribution/bin/mqadmin.cmd index 4e061f0ef93..a28facb1ff5 100644 --- a/distribution/bin/mqadmin.cmd +++ b/distribution/bin/mqadmin.cmd @@ -15,4 +15,4 @@ 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" org.apache.rocketmq.tools.command.MQAdminStartup %* \ No newline at end of file +call "%ROCKETMQ_HOME%\bin\tools.cmd" -Drmq.logback.configurationFile=%ROCKETMQ_HOME%\conf\rmq.tools.logback.xml org.apache.rocketmq.tools.command.MQAdminStartup %* \ No newline at end of file diff --git a/distribution/bin/mqbroker b/distribution/bin/mqbroker index 6a79c392e8d..3758ed597c0 100644 --- a/distribution/bin/mqbroker +++ b/distribution/bin/mqbroker @@ -42,4 +42,37 @@ fi export ROCKETMQ_HOME -sh ${ROCKETMQ_HOME}/bin/runbroker.sh org.apache.rocketmq.broker.BrokerStartup $@ +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/runserver.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 \ No newline at end of file diff --git a/distribution/bin/mqbroker.cmd b/distribution/bin/mqbroker.cmd index 3efb47577cc..644e217a2f0 100644 --- a/distribution/bin/mqbroker.cmd +++ b/distribution/bin/mqbroker.cmd @@ -16,7 +16,7 @@ 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" org.apache.rocketmq.broker.BrokerStartup %* +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" diff --git a/distribution/bin/mqbrokercontainer b/distribution/bin/mqbrokercontainer new file mode 100644 index 00000000000..0ce383f0219 --- /dev/null +++ b/distribution/bin/mqbrokercontainer @@ -0,0 +1,45 @@ +#!/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 $@ diff --git a/distribution/bin/mqcontroller b/distribution/bin/mqcontroller new file mode 100644 index 00000000000..5ac064d43e3 --- /dev/null +++ b/distribution/bin/mqcontroller @@ -0,0 +1,45 @@ +#!/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 $@ diff --git a/distribution/bin/mqcontroller.cmd b/distribution/bin/mqcontroller.cmd new file mode 100644 index 00000000000..95fae9724dd --- /dev/null +++ b/distribution/bin/mqcontroller.cmd @@ -0,0 +1,23 @@ +@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" +) \ No newline at end of file diff --git a/distribution/bin/mqnamesrv b/distribution/bin/mqnamesrv index c1e70bde8df..6741c7f00b8 100644 --- a/distribution/bin/mqnamesrv +++ b/distribution/bin/mqnamesrv @@ -42,4 +42,4 @@ fi export ROCKETMQ_HOME -sh ${ROCKETMQ_HOME}/bin/runserver.sh org.apache.rocketmq.namesrv.NamesrvStartup $@ +sh ${ROCKETMQ_HOME}/bin/runserver.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.namesrv.logback.xml org.apache.rocketmq.namesrv.NamesrvStartup $@ diff --git a/distribution/bin/mqnamesrv.cmd b/distribution/bin/mqnamesrv.cmd index 2828bdc28d0..97219d86e3b 100644 --- a/distribution/bin/mqnamesrv.cmd +++ b/distribution/bin/mqnamesrv.cmd @@ -16,7 +16,7 @@ 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" org.apache.rocketmq.namesrv.NamesrvStartup %* +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" diff --git a/distribution/bin/mqproxy b/distribution/bin/mqproxy new file mode 100644 index 00000000000..d6a8f3f268e --- /dev/null +++ b/distribution/bin/mqproxy @@ -0,0 +1,45 @@ +#!/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 $@ diff --git a/distribution/bin/mqproxy.cmd b/distribution/bin/mqproxy.cmd new file mode 100644 index 00000000000..51f7b2118a3 --- /dev/null +++ b/distribution/bin/mqproxy.cmd @@ -0,0 +1,23 @@ +@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" +) \ No newline at end of file diff --git a/distribution/bin/mqshutdown b/distribution/bin/mqshutdown index d2d51fc68ec..f4b58e2b79e 100644 --- a/distribution/bin/mqshutdown +++ b/distribution/bin/mqshutdown @@ -17,11 +17,16 @@ 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; + exit 1; fi echo "The mqbroker(${pid}) is running..." @@ -30,12 +35,26 @@ case $1 in 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; + exit 1; fi echo "The mqnamesrv(${pid}) is running..." @@ -44,6 +63,34 @@ case $1 in 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 "Useage: mqshutdown broker | namesrv" + echo "Usage: mqshutdown broker | namesrv | controller | proxy" esac diff --git a/distribution/bin/mqshutdown.cmd b/distribution/bin/mqshutdown.cmd index 50af026f7c0..32fcc997eb4 100644 --- a/distribution/bin/mqshutdown.cmd +++ b/distribution/bin/mqshutdown.cmd @@ -29,7 +29,13 @@ if /I "%1" == "broker" ( 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 + echo Unknown role to kill, please specify broker or namesrv or controller ) \ No newline at end of file diff --git a/distribution/bin/runbroker.cmd b/distribution/bin/runbroker.cmd index e52230708e3..15f676aa81a 100644 --- a/distribution/bin/runbroker.cmd +++ b/distribution/bin/runbroker.cmd @@ -36,6 +36,7 @@ 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% -cp %CLASSPATH%" +set "JAVA_OPT=%JAVA_OPT% -Drocketmq.client.logUseSlf4j=true" +set "JAVA_OPT=%JAVA_OPT% %JAVA_OPT_EXT% -cp %CLASSPATH%" "%JAVA%" %JAVA_OPT% %* \ No newline at end of file diff --git a/distribution/bin/runbroker.sh b/distribution/bin/runbroker.sh index b9048ec3c6c..a081df79e49 100644 --- a/distribution/bin/runbroker.sh +++ b/distribution/bin/runbroker.sh @@ -24,6 +24,24 @@ error_exit () 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)!" @@ -66,7 +84,7 @@ 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} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC" + 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 @@ -88,6 +106,7 @@ 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" +JAVA_OPT="${JAVA_OPT} -Drocketmq.client.logUseSlf4j=true" #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}" @@ -101,5 +120,5 @@ then numactl --cpunodebind=$RMQ_NUMA_NODE --membind=$RMQ_NUMA_NODE $JAVA ${JAVA_OPT} $@ fi else - $JAVA ${JAVA_OPT} $@ + "$JAVA" ${JAVA_OPT} $@ fi diff --git a/distribution/bin/runserver.cmd b/distribution/bin/runserver.cmd index 2bea8edd9c4..dc2e2b4e224 100644 --- a/distribution/bin/runserver.cmd +++ b/distribution/bin/runserver.cmd @@ -31,6 +31,6 @@ set "JAVA_OPT=%JAVA_OPT% -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollect 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% -cp "%CLASSPATH%"" +set "JAVA_OPT=%JAVA_OPT% %JAVA_OPT_EXT% -cp "%CLASSPATH%"" "%JAVA%" %JAVA_OPT% %* \ No newline at end of file diff --git a/distribution/bin/runserver.sh b/distribution/bin/runserver.sh index 47cf0f009b2..76ac9374a0c 100644 --- a/distribution/bin/runserver.sh +++ b/distribution/bin/runserver.sh @@ -24,6 +24,24 @@ error_exit () 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)!" @@ -66,7 +84,7 @@ choose_gc_options() { # Example of JAVA_MAJOR_VERSION value : '1', '9', '10', '11', ... # '1' means releases befor Java 9 - JAVA_MAJOR_VERSION=$("$JAVA" -version 2>&1 | sed -r -n 's/.* version "([0-9]*).*$/\1/p') + 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" @@ -87,4 +105,4 @@ JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages" JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}" JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}" -$JAVA ${JAVA_OPT} $@ +"$JAVA" ${JAVA_OPT} $@ diff --git a/distribution/bin/tools.sh b/distribution/bin/tools.sh index d3fa7eb65e0..d772465a3d4 100644 --- a/distribution/bin/tools.sh +++ b/distribution/bin/tools.sh @@ -24,6 +24,24 @@ error_exit () 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)!" @@ -39,4 +57,4 @@ export CLASSPATH=.:${BASE_DIR}/conf:${BASE_DIR}/lib/*:${CLASSPATH} JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m" JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}" -$JAVA ${JAVA_OPT} "$@" +"$JAVA" ${JAVA_OPT} "$@" diff --git a/distribution/conf/container/2container-2m-2s/broker-a-in-container1.conf b/distribution/conf/container/2container-2m-2s/broker-a-in-container1.conf new file mode 100644 index 00000000000..8da6011a5a0 --- /dev/null +++ b/distribution/conf/container/2container-2m-2s/broker-a-in-container1.conf @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 \ No newline at end of file diff --git a/distribution/conf/container/2container-2m-2s/broker-a-in-container2.conf b/distribution/conf/container/2container-2m-2s/broker-a-in-container2.conf new file mode 100644 index 00000000000..c654bf8c688 --- /dev/null +++ b/distribution/conf/container/2container-2m-2s/broker-a-in-container2.conf @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 \ No newline at end of file diff --git a/distribution/conf/container/2container-2m-2s/broker-b-in-container1.conf b/distribution/conf/container/2container-2m-2s/broker-b-in-container1.conf new file mode 100644 index 00000000000..6e8f896d174 --- /dev/null +++ b/distribution/conf/container/2container-2m-2s/broker-b-in-container1.conf @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 \ No newline at end of file diff --git a/distribution/conf/container/2container-2m-2s/broker-b-in-container2.conf b/distribution/conf/container/2container-2m-2s/broker-b-in-container2.conf new file mode 100644 index 00000000000..cf9f803ea6d --- /dev/null +++ b/distribution/conf/container/2container-2m-2s/broker-b-in-container2.conf @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 \ No newline at end of file diff --git a/distribution/conf/container/2container-2m-2s/broker-container1.conf b/distribution/conf/container/2container-2m-2s/broker-container1.conf new file mode 100644 index 00000000000..f50165d57c9 --- /dev/null +++ b/distribution/conf/container/2container-2m-2s/broker-container1.conf @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 \ No newline at end of file diff --git a/distribution/conf/container/2container-2m-2s/broker-container2.conf b/distribution/conf/container/2container-2m-2s/broker-container2.conf new file mode 100644 index 00000000000..0870bfdf775 --- /dev/null +++ b/distribution/conf/container/2container-2m-2s/broker-container2.conf @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 \ No newline at end of file diff --git a/distribution/conf/container/2container-2m-2s/nameserver.conf b/distribution/conf/container/2container-2m-2s/nameserver.conf new file mode 100644 index 00000000000..fd700c18748 --- /dev/null +++ b/distribution/conf/container/2container-2m-2s/nameserver.conf @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 \ No newline at end of file diff --git a/distribution/conf/controller/cluster-3n-independent/controller-n0.conf b/distribution/conf/controller/cluster-3n-independent/controller-n0.conf new file mode 100644 index 00000000000..d5741379205 --- /dev/null +++ b/distribution/conf/controller/cluster-3n-independent/controller-n0.conf @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + diff --git a/distribution/conf/controller/cluster-3n-independent/controller-n1.conf b/distribution/conf/controller/cluster-3n-independent/controller-n1.conf new file mode 100644 index 00000000000..f6dec223551 --- /dev/null +++ b/distribution/conf/controller/cluster-3n-independent/controller-n1.conf @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + diff --git a/distribution/conf/controller/cluster-3n-independent/controller-n2.conf b/distribution/conf/controller/cluster-3n-independent/controller-n2.conf new file mode 100644 index 00000000000..aa45fa53cc3 --- /dev/null +++ b/distribution/conf/controller/cluster-3n-independent/controller-n2.conf @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + diff --git a/distribution/conf/controller/cluster-3n-namesrv-plugin/namesrv-n0.conf b/distribution/conf/controller/cluster-3n-namesrv-plugin/namesrv-n0.conf new file mode 100644 index 00000000000..ee2f5dfc583 --- /dev/null +++ b/distribution/conf/controller/cluster-3n-namesrv-plugin/namesrv-n0.conf @@ -0,0 +1,25 @@ + + +#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 \ No newline at end of file diff --git a/distribution/conf/controller/cluster-3n-namesrv-plugin/namesrv-n1.conf b/distribution/conf/controller/cluster-3n-namesrv-plugin/namesrv-n1.conf new file mode 100644 index 00000000000..9461321a291 --- /dev/null +++ b/distribution/conf/controller/cluster-3n-namesrv-plugin/namesrv-n1.conf @@ -0,0 +1,25 @@ + + +#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 \ No newline at end of file diff --git a/distribution/conf/controller/cluster-3n-namesrv-plugin/namesrv-n2.conf b/distribution/conf/controller/cluster-3n-namesrv-plugin/namesrv-n2.conf new file mode 100644 index 00000000000..aee7e9947d8 --- /dev/null +++ b/distribution/conf/controller/cluster-3n-namesrv-plugin/namesrv-n2.conf @@ -0,0 +1,25 @@ + + +#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 \ No newline at end of file diff --git a/distribution/conf/controller/controller-standalone.conf b/distribution/conf/controller/controller-standalone.conf new file mode 100644 index 00000000000..700908f3446 --- /dev/null +++ b/distribution/conf/controller/controller-standalone.conf @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + diff --git a/distribution/conf/controller/quick-start/broker-n0.conf b/distribution/conf/controller/quick-start/broker-n0.conf new file mode 100644 index 00000000000..c397689abde --- /dev/null +++ b/distribution/conf/controller/quick-start/broker-n0.conf @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 \ No newline at end of file diff --git a/distribution/conf/controller/quick-start/broker-n1.conf b/distribution/conf/controller/quick-start/broker-n1.conf new file mode 100644 index 00000000000..33bab6b387a --- /dev/null +++ b/distribution/conf/controller/quick-start/broker-n1.conf @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 \ No newline at end of file diff --git a/distribution/conf/controller/quick-start/namesrv.conf b/distribution/conf/controller/quick-start/namesrv.conf new file mode 100644 index 00000000000..a7d81b0d8c9 --- /dev/null +++ b/distribution/conf/controller/quick-start/namesrv.conf @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 \ No newline at end of file diff --git a/distribution/conf/acl/plain_acl.yml b/distribution/conf/plain_acl.yml similarity index 100% rename from distribution/conf/acl/plain_acl.yml rename to distribution/conf/plain_acl.yml diff --git a/distribution/conf/rmq-proxy.json b/distribution/conf/rmq-proxy.json new file mode 100644 index 00000000000..8e92bb18e52 --- /dev/null +++ b/distribution/conf/rmq-proxy.json @@ -0,0 +1,3 @@ +{ + "rocketMQClusterName": "DefaultCluster" +} \ No newline at end of file diff --git a/distribution/pom.xml b/distribution/pom.xml index da6e83ca5df..1269e160098 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -20,20 +20,32 @@ org.apache.rocketmq rocketmq-all - 4.9.4-SNAPSHOT + 5.1.3 rocketmq-distribution rocketmq-distribution ${project.version} pom + + ${basedir}/.. + + release-all + + org.apache.rocketmq + rocketmq-container + org.apache.rocketmq rocketmq-broker + + org.apache.rocketmq + rocketmq-proxy + org.apache.rocketmq rocketmq-client @@ -79,7 +91,6 @@ org.apache.rocketmq rocketmq-client - ${project.version} diff --git a/distribution/release.xml b/distribution/release.xml index fd6e3db9427..b7710425d06 100644 --- a/distribution/release.xml +++ b/distribution/release.xml @@ -55,18 +55,44 @@ 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/ diff --git a/docs/cn/BrokerContainer.md b/docs/cn/BrokerContainer.md new file mode 100644 index 00000000000..236439284be --- /dev/null +++ b/docs/cn/BrokerContainer.md @@ -0,0 +1,128 @@ +# 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}`。 \ No newline at end of file diff --git a/docs/cn/Configuration_System.md b/docs/cn/Configuration_System.md index d72ef938136..b85d365bd65 100644 --- a/docs/cn/Configuration_System.md +++ b/docs/cn/Configuration_System.md @@ -45,7 +45,7 @@ -- **vm.extra_free_kbytes**, 控制VM在后台回收(kswapd)开始的阈值和直接回收(通过分配进程)开始的阈值之间保留额外的空闲内存。通过使用这个参数,RocketMQ 可以避免在内存分配过程中出现高延迟。(与内核版本版本有关) +- **vm.extra_free_kbytes**, 控制VM在后台回收(kswapd)开始的阈值和直接回收(通过分配进程)开始的阈值之间保留额外的空闲内存。通过使用这个参数,RocketMQ 可以避免在内存分配过程中出现高延迟。(与内核版本有关) diff --git a/docs/cn/Configuration_TLS.md b/docs/cn/Configuration_TLS.md new file mode 100644 index 00000000000..9ff03e53a2e --- /dev/null +++ b/docs/cn/Configuration_TLS.md @@ -0,0 +1,119 @@ +# 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 +``` + +如果需要客户端连接时也进行认证,则还需要在该文件中增加以下内容 +```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 配置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 +``` + +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(); + } +} +``` \ No newline at end of file diff --git a/docs/cn/Deployment.md b/docs/cn/Deployment.md index 1f00986b4be..14529d111b0 100644 --- a/docs/cn/Deployment.md +++ b/docs/cn/Deployment.md @@ -25,7 +25,7 @@ The Name Server boot success... ### 第一步先启动broker $ nohup sh bin/mqbroker -n localhost:9876 & -### 验证broker是否启动成功, 比如, broker的ip是192.168.1.2 然后名字是broker-a +### 验证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... ``` @@ -36,12 +36,12 @@ The broker[broker-a,192.169.1.2:10911] boot success... 该模式是指所有节点都是master主节点(比如2个或3个主节点),没有slave从节点的模式。 这种模式的优缺点如下: -- 优点: +- 优点: 1. 配置简单。 2. 一个master节点的宕机或者重启(维护)对应用程序没有影响。 3. 当磁盘配置为RAID10时,消息不会丢失,因为RAID10磁盘非常可靠,即使机器不可恢复(消息异步刷盘模式的情况下,会丢失少量消息;如果消息是同步刷盘模式,不会丢失任何消息)。 4. 在这种模式下,性能是最高的。 -- 缺点: +- 缺点: 1. 单台机器宕机时,本机未消费的消息,直到机器恢复后才会订阅,影响消息实时性。 多Master模式的启动步骤如下: @@ -49,7 +49,7 @@ The broker[broker-a,192.169.1.2:10911] boot success... **1)启动 NameServer** ```shell -### 第一步先启动broker +### 第一步先启动namesrv $ nohup sh mqnamesrv & ### 验证namesrv是否启动成功 @@ -69,17 +69,17 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker ... ``` -上面显示的boot命令用于单个NameServer的情况。对于多个NameServer的集群,broker boot命令中-n参数后面的地址列表用分号隔开,例如 192.168.1.1 : 9876; 192.161.2 : 9876 +上面显示的启动命令用于单个NameServer的情况。对于多个NameServer的集群,broker 启动命令中-n参数后面的地址列表用分号隔开,例如 192.168.1.1:9876;192.161.2:9876 ### 3 多Master多Slave模式-异步复制 每个主节点配置多个从节点,多对主从。HA采用异步复制,主节点和从节点之间有短消息延迟(毫秒)。这种模式的优缺点如下: -- 优点: - 1. 即使磁盘损坏,也不会丢失极少的消息,不影响消息的实时性能。 +- 优点: + 1. 即使磁盘损坏,也只会丢失极少的消息,不影响消息的实时性能。 2. 同时,当主节点宕机时,消费者仍然可以消费从节点的消息,这个过程对应用本身是透明的,不需要人为干预。 3. 性能几乎与多Master模式一样高。 -- 缺点: +- 缺点: 1. 主节点宕机、磁盘损坏时,会丢失少量消息。 多主多从模式的启动步骤如下: @@ -87,7 +87,7 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker **1)启动 NameServer** ```shell -### 第一步先启动broker +### 第一步先启动namesrv $ nohup sh mqnamesrv & ### 验证namesrv是否启动成功 @@ -119,11 +119,11 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broke 这种模式的优缺点如下: -- 优点: +- 优点: 1. 数据和服务都没有单点故障。 2. 在master节点关闭的情况下,消息也没有延迟。 3. 服务可用性和数据可用性非常高; -- 缺点: +- 缺点: 1. 这种模式下的性能略低于异步复制模式(大约低 10%)。 2. 发送单条消息的RT略高,目前版本,master节点宕机后,slave节点无法自动切换到master。 @@ -132,7 +132,7 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broke **1)启动NameServer** ```shell -### 第一步启动broker +### 第一步启动namesrv $ nohup sh mqnamesrv & ### 验证namesrv是否启动成功 @@ -156,4 +156,16 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker $ 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。 \ No newline at end of file +上述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) + + diff --git a/docs/cn/Example_Batch.md b/docs/cn/Example_Batch.md index 6c8897fa6bc..4edac9a326e 100644 --- a/docs/cn/Example_Batch.md +++ b/docs/cn/Example_Batch.md @@ -27,10 +27,12 @@ public class ListSplitter implements Iterator> { public ListSplitter(List messages) { this.messages = messages; } - @Override public boolean hasNext() { + @Override + public boolean hasNext() { return currIndex < messages.size(); } - @Override public List next() { + @Override + public List next() { int startIndex = getStartIndex(); int nextIndex = startIndex; int totalSize = 0; diff --git a/docs/cn/Example_Compaction_Topic_cn.md b/docs/cn/Example_Compaction_Topic_cn.md new file mode 100644 index 00000000000..6ebb5a986d2 --- /dev/null +++ b/docs/cn/Example_Compaction_Topic_cn.md @@ -0,0 +1,73 @@ +# 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 +``` \ No newline at end of file diff --git a/docs/cn/Example_CreateTopic.md b/docs/cn/Example_CreateTopic.md new file mode 100644 index 00000000000..ee975290edf --- /dev/null +++ b/docs/cn/Example_CreateTopic.md @@ -0,0 +1,26 @@ +# 创建主题 + +## 背景 + +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 +``` diff --git a/docs/cn/QuorumACK.md b/docs/cn/QuorumACK.md new file mode 100644 index 00000000000..bbeb94189d6 --- /dev/null +++ b/docs/cn/QuorumACK.md @@ -0,0 +1,73 @@ +# 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) \ No newline at end of file diff --git a/docs/cn/README.md b/docs/cn/README.md index 2dbd8549133..acfbd2f69ff 100644 --- a/docs/cn/README.md +++ b/docs/cn/README.md @@ -1,7 +1,7 @@ Apache RocketMQ开发者指南 -------- -##### 这个开发者指南是帮助您快速了解,并使用 Apache RocketMQ +##### 这个开发者指南旨在帮助您快速了解并使用 Apache RocketMQ ### 1. 概念和特性 @@ -21,22 +21,28 @@ - [样例(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集群。 - -- [Dledger快速搭建(Quick Start)](dledger/quick_start.md):介绍Dledger的快速搭建方法。 - -- [集群部署(Cluster Deployment)](dledger/deploy_guide.md):介绍Dledger的集群部署方式。 +- [自动主从切换快速开始](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) -### 6. API Reference(待补充) +### 7. API Reference(待补充) - [DefaultMQProducer API Reference](client/java/API_Reference_DefaultMQProducer.md) diff --git a/docs/cn/RocketMQ_Example.md b/docs/cn/RocketMQ_Example.md index e26bcdf043d..ece3a56f229 100644 --- a/docs/cn/RocketMQ_Example.md +++ b/docs/cn/RocketMQ_Example.md @@ -440,6 +440,8 @@ 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", "*"); // 注册消息监听者 @@ -471,6 +473,8 @@ 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; @@ -541,10 +545,12 @@ public class ListSplitter implements Iterator> { public ListSplitter(List messages) { this.messages = messages; } - @Override public boolean hasNext() { + @Override + public boolean hasNext() { return currIndex < messages.size(); } - @Override public List next() { + @Override + public List next() { int startIndex = getStartIndex(); int nextIndex = startIndex; int totalSize = 0; @@ -566,7 +572,7 @@ public class ListSplitter implements Iterator> { int tmpSize = calcMessageSize(currMessage); while(tmpSize > SIZE_LIMIT) { currIndex += 1; - Message message = messages.get(curIndex); + Message message = messages.get(currIndex); tmpSize = calcMessageSize(message); } return currIndex; @@ -861,50 +867,52 @@ 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(Charset.forName("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(Charset.forName("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(Charset.forName("UTF-8")))); - System.out.printf("Send oneway message OK%n"); - } - try { - countDownLatch.await(); - Thread.sleep(500); // 等一些时间来发送消息 - } catch (InterruptedException ignore) { - } - producer.shutdown(); - } + 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(); + } } ``` diff --git a/docs/cn/SlaveActingMasterMode.md b/docs/cn/SlaveActingMasterMode.md new file mode 100644 index 00000000000..b1e266f2b42 --- /dev/null +++ b/docs/cn/SlaveActingMasterMode.md @@ -0,0 +1,164 @@ +# 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信息上报,过于“重”,而且注册间隔过于长,因此需要一个轻量级的心跳机制,RoccketMQ 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的信息,RoccketMQ 5.0会新增GetBrokerMemberGroup请求来完成该工作。 + +Slave Broker发现自己是该组中最小的brokerId,将会开启代理模式,而一旦Master Broker重新上线,Slave Broker同样会通过Nameserver反向通知或自身定时任务同步同组broker的信息感知到,并自动结束代理模式。 + +### 二级消息逃逸 + +代理模式开启后,brokerId最小的Slave会承担起二级消息的扫描和重新投递功能。 + +二级消息一般分为两个阶段,发送或者消费时会发送到一个特殊topic中,后台会有线程会扫描,最终的满足要求的消息会被重新投递到Commitlog中。我们可以让brokerId最小的Slave进行扫描,但如果扫描之后的消息重新投递到本Commitlog,那将会破坏Slave不可写的语义,造成Commitlog分叉。因此RoccketMQ 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) \ No newline at end of file diff --git "a/docs/cn/acl/RocketMQ_Multiple_ACL_Files_\350\256\276\350\256\241.md" "b/docs/cn/acl/RocketMQ_Multiple_ACL_Files_\350\256\276\350\256\241.md" index 9e11be554c7..d457b67566d 100644 --- "a/docs/cn/acl/RocketMQ_Multiple_ACL_Files_\350\256\276\350\256\241.md" +++ "b/docs/cn/acl/RocketMQ_Multiple_ACL_Files_\350\256\276\350\256\241.md" @@ -38,7 +38,7 @@ dataVersionMap是个Map类型,用来缓存所有ACL配置文件的DataVersion ### 4.1 加载ACL配置文件 - load() -load()方法会获取"RocketMQ安装目录/conf"目录(包括该目录的子目录)和"rocketmq.acl.plain.file"下所有ACL配置文件,然后遍历这些文件读取权限数据和全局白名单。 +load()方法会获取"RocketMQ安装目录/conf/acl"目录(包括该目录的子目录)和"rocketmq.acl.plain.file"下所有ACL配置文件,然后遍历这些文件读取权限数据和全局白名单。 - load(String aclFilePath) load(String aclFilePath)方法完成加载指定ACL配置文件内容的功能,将配置文件中的全局白名单globalWhiteRemoteAddresses和用户权限accounts加载到缓存中, @@ -48,7 +48,7 @@ load(String aclFilePath)方法完成加载指定ACL配置文件内容的功能 (2)相同的accessKey只允许存在在一个ACL配置文件中 ### 4.2 监控ACL配置文件 -watch()方法用来监控"RocketMQ安装目录/conf"目录下所有ACL配置文件和"rocketmq.acl.plain.file"是否发生变化,变化考虑两种情况:一种是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 @@ -69,9 +69,9 @@ ACL配置文件的数量aclFilesNum、所有ACL配置文件绝对路径fileList 再根据该路径更新aclPlainAccessResourceMap中缓存的数据,最后将该ACL配置文件中的数据写回原文件;如果不包含则会将数据写到"rocketmq.acl.plain.file"配置文件中, 然后更新accessKeyTable和aclPlainAccessResourceMap,最后最后将该ACL配置文件中的数据写回原文件。 -(3)deleteAccessConfig(String accesskey) +(3)deleteAccessConfig(String accessKey) -将该方法原有的逻辑修改为:判断accessKeyTable中是否存在accesskey,如果不存在则返回false,否则将其删除并将修改后的数据写回原文件。 +将该方法原有的逻辑修改为:判断accessKeyTable中是否存在accessKey,如果不存在则返回false,否则将其删除并将修改后的数据写回原文件。 (4)getAllAclConfig() @@ -122,7 +122,7 @@ key表示各ACL配置文件的绝对路径,value表示对应配置文件的版 由于PlainAccessValidator实现了AccessValidator接口,所以相应地增加了getAllAclConfigVersion()方法 # 后续扩展性考虑 -1.目前的修改只支持ACL配置文件存储在"RocketMQ安装目录/conf"目录下,后续可以考虑支持多目录; +1.目前的修改只支持ACL配置文件存储在"RocketMQ安装目录/conf/acl"目录下,后续可以考虑支持多目录; 2.目前ACL配置文件路径是不支持让用户指定,后续可以考虑让用户指定指定ACL配置文件的存储路径 diff --git a/docs/cn/acl/user_guide.md b/docs/cn/acl/user_guide.md index 4ee03ba60bd..463a28d8ce4 100644 --- a/docs/cn/acl/user_guide.md +++ b/docs/cn/acl/user_guide.md @@ -155,7 +155,7 @@ sh mqadmin clusterAclConfigVersion -n 192.168.1.2:9876 -c DefaultCluster ### 7.5 查询集群/Broker的ACL配置文件全部内容 该命令的示例如下: -sh mqadmin getAccessConfigSubCommand -n 192.168.1.2:9876 -c DefaultCluster +sh mqadmin getAclConfig -n 192.168.1.2:9876 -c DefaultCluster 说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 @@ -166,4 +166,4 @@ sh mqadmin getAccessConfigSubCommand -n 192.168.1.2:9876 -c DefaultCluster | b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) | **特别注意**开启Acl鉴权认证后导致Master/Slave和Dledger模式下Broker同步数据异常的问题, -在社区[4.5.1]版本中已经修复,具体的PR链接为:https://github.com/apache/rocketmq/pull/1149; \ No newline at end of file +在社区[4.5.1]版本中已经修复,具体的PR链接为:#1149。 diff --git a/docs/cn/architecture.md b/docs/cn/architecture.md index 942464f94ac..87e93d18329 100644 --- a/docs/cn/architecture.md +++ b/docs/cn/architecture.md @@ -3,13 +3,13 @@ ## 1 技术架构 ![](image/rocketmq_architecture_1.png) -RocketMQ架构上主要分为四部分,如上图所示: +RocketMQ架构上主要分为四部分,如上图所示: - Producer:消息发布的角色,支持分布式集群方式部署。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程支持快速失败并且低延迟。 - Consumer:消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制,可以满足大多数用户的需求。 -- NameServer:NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。主要包括两个功能:Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer和Consumer仍然可以动态感知Broker的路由的信息。 +- 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端的请求。 diff --git a/docs/cn/best_practice.md b/docs/cn/best_practice.md index caf0e0ac208..5cc5b37643f 100755 --- a/docs/cn/best_practice.md +++ b/docs/cn/best_practice.md @@ -184,8 +184,8 @@ msgId一定是全局唯一标识符,但是实际使用中,可能会存在相 | brokerIP1 | 网卡的 InetAddress | 当前 broker 监听的 IP | | brokerIP2 | 跟 brokerIP1 一样 | 存在主从 broker 时,如果在 broker 主节点上配置了 brokerIP2 属性,broker 从节点会连接主节点配置的 brokerIP2 进行同步 | | brokerName | null | broker 的名称 | -| brokerClusterName | DefaultCluster | 本 broker 所属的 Cluser 名称 | -| brokerId | 0 | broker id, 0 表示 master, 其他的正整数表示 slave | +| 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 的映射文件大小 |​ @@ -209,7 +209,7 @@ msgId一定是全局唯一标识符,但是实际使用中,可能会存在相 ### 5.1 客户端寻址方式 -RocketMQ可以令客户端找到Name Server, 然后通过Name Server再找到Broker。如下所示有多种配置方式,优先级由高到低,高优先级会覆盖低优先级。 +RocketMQ可以令客户端找到Name Server,然后通过Name Server再找到Broker。如下所示有多种配置方式,优先级由高到低,高优先级会覆盖低优先级。 - 代码中指定Name Server地址,多个namesrv地址之间用分号分割 @@ -268,7 +268,7 @@ DefaultMQProducer、TransactionMQProducer、DefaultMQPushConsumer、DefaultMQPul | compressMsgBodyOverHowmuch | 4096 | 消息Body超过多大开始压缩(Consumer收到消息会自动解压缩),单位字节 | | retryAnotherBrokerWhenNotStoreOK | FALSE | 如果发送消息返回sendResult,但是sendStatus!=SEND_OK,是否重试发送 | | retryTimesWhenSendFailed | 2 | 如果消息发送失败,最大重试次数,该参数只对同步发送模式起作用 | -| maxMessageSize | 4MB | 客户端限制的消息大小,超过报错,同时服务端也会限制,所以需要跟服务端配合使用。 | +| maxMessageSize | 4MB | 客户端限制的消息体大小,超过报错,同时服务端也会限制,所以需要跟服务端配合使用。 | | transactionCheckListener | | 事务消息回查监听器,如果发送事务消息,必须设置 | | checkThreadPoolMinSize | 1 | Broker回查Producer事务状态时,线程池最小线程数 | | checkThreadPoolMaxSize | 1 | Broker回查Producer事务状态时,线程池最大线程数 | diff --git a/docs/cn/client/java/API_Reference_ DefaultPullConsumer.md b/docs/cn/client/java/API_Reference_ DefaultPullConsumer.md index 64a2b7bd80f..66857e2fd2e 100644 --- a/docs/cn/client/java/API_Reference_ DefaultPullConsumer.md +++ b/docs/cn/client/java/API_Reference_ DefaultPullConsumer.md @@ -4,11 +4,11 @@ 1. `DefaultMQPullConsumer extends ClientConfig implements MQPullConsumer` -2. `DefaultMQPullConsumer`主动的从Broker拉取消息,主动权由应用控制,可以实现批量的消费消息。Pull方式取消息的过程需要用户自己写,首先通过打算消费的Topic拿到MessageQueue的集合,遍历MessageQueue集合,然后针对每个MessageQueue批量取消息,也可以自定义与控制offset位置。 +2. `DefaultMQPullConsumer`主动的从Broker拉取消息,主动权由应用控制,可以实现批量的消费消息。Pull方式取消息的过程需要用户自己写,首先通过打算消费的Topic拿到MessageQueue的集合,遍历MessageQueue集合,然后针对每个MessageQueue批量取消息,也可以自定义与控制offset位置。 -3. 优势:consumer可以按需消费,不用担心自己处理能力,而broker堆积消息也会相对简单,无需记录每一个要发送消息的状态,只需要维护所有消息的队列和偏移量就可以了。所以对于慢消费,消息量有限且到来的速度不均匀的情况,pull模式比较合适消息延迟与忙等。 +3. 优势:consumer可以按需消费,不用担心自己处理能力,而broker堆积消息也会相对简单,无需记录每一个要发送消息的状态,只需要维护所有消息的队列和偏移量就可以了。所以对于慢消费,消息量有限且到来的速度不均匀的情况,pull模式比较合适消息延迟与忙等。 -4. 缺点:由于主动权在消费方,消费方无法及时获取最新的消息。比较适合不及时批处理场景。 +4. 缺点:由于主动权在消费方,消费方无法及时获取最新的消息。比较适合不及时批处理场景。 ``` java @@ -112,32 +112,32 @@ public class MQPullConsumer { ### 使用方法摘要 -|返回值|方法名称|方法描述| -|-------|-------|------------| -|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()|启动消费者| +|返回值|方法名称| 方法描述 | +|-------|-------|----------------------------------------------------------------------------------------| +|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()| 启动消费者 | diff --git a/docs/cn/client/java/API_Reference_DefaultMQProducer.md b/docs/cn/client/java/API_Reference_DefaultMQProducer.md index 4a0b9d5f6c1..36ad323bd1a 100644 --- a/docs/cn/client/java/API_Reference_DefaultMQProducer.md +++ b/docs/cn/client/java/API_Reference_DefaultMQProducer.md @@ -53,7 +53,7 @@ public class Producer { |int|retryTimesWhenSendFailed|同步模式下内部尝试发送消息的最大次数| |int|retryTimesWhenSendAsyncFailed|异步模式下内部尝试发送消息的最大次数| |boolean|retryAnotherBrokerWhenNotStoreOK|是否在内部发送失败时重试另一个broker| -|int|maxMessageSize|消息的最大长度| +|int|maxMessageSize|消息体的最大长度| |TraceDispatcher|traceDispatcher|基于RPCHooK实现的消息轨迹插件| ### 构造方法摘要 @@ -108,7 +108,7 @@ public class Producer { ### 字段详细信息 -- [producerGroup](http://rocketmq.apache.org/docs/core-concept/) +- [producerGroup](https://rocketmq.apache.org/docs/introduction/02concepts) `private String producerGroup` @@ -196,7 +196,7 @@ public class Producer { `private int maxMessageSize = 1024 * 1024 * 4` - 消息的最大大小。当消息题的字节数超过maxMessageSize就发送失败。 + 消息体的最大大小。当消息体的字节数超过maxMessageSize就发送失败。 默认值:1024 * 1024 * 4,单位:字节 diff --git a/docs/cn/concept.md b/docs/cn/concept.md index 75532e89da1..cb2c863bde9 100644 --- a/docs/cn/concept.md +++ b/docs/cn/concept.md @@ -23,7 +23,7 @@ RocketMQ主要由 Producer、Broker、Consumer 三部分组成,其中Producer Consumer消费的一种类型,应用通常主动调用Consumer的拉消息方法从Broker服务器拉消息、主动权由应用控制。一旦获取了批量消息,应用就会启动消费过程。 ## 8 推动式消费(Push Consumer) - Consumer消费的一种类型,该模式下Broker收到数据后会主动推送给消费端,该消费模式一般实时性较高。 + Consumer消费的一种类型,应用不需要主动调用Consumer的拉消息方法,在底层已经封装了拉取的调用逻辑,在用户层面看来是broker把消息推送过来的,其实底层还是consumer去broker主动拉取消息。 ## 9 生产者组(Producer Group) 同一类Producer的集合,这类Producer发送同一类消息且发送逻辑一致。如果发送的是事务消息且原始生产者在发送之后崩溃,则Broker服务器会联系同一生产者组的其他生产者实例以提交或回溯消费。 @@ -32,7 +32,7 @@ RocketMQ主要由 Producer、Broker、Consumer 三部分组成,其中Producer 同一类Consumer的集合,这类Consumer通常消费同一类消息且消费逻辑一致。消费者组使得在消息消费方面,实现负载均衡和容错的目标变得非常容易。要注意的是,消费者组的消费者实例必须订阅完全相同的Topic。RocketMQ 支持两种消息模式:集群消费(Clustering)和广播消费(Broadcasting)。 ## 11 集群消费(Clustering) -集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊消息。 +集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊消息。 ## 12 广播消费(Broadcasting) 广播消费模式下,相同Consumer Group的每个Consumer实例都接收全量的消息。 diff --git a/docs/cn/controller/deploy.md b/docs/cn/controller/deploy.md new file mode 100644 index 00000000000..fa599f3dccc --- /dev/null +++ b/docs/cn/controller/deploy.md @@ -0,0 +1,164 @@ +# 部署和升级指南 + +## 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代码。 + +参数设置完成后,指定配置文件启动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对齐,否则可能导致数据被截断而丢失。 \ No newline at end of file diff --git a/docs/cn/controller/design.md b/docs/cn/controller/design.md new file mode 100644 index 00000000000..a8d18dd675f --- /dev/null +++ b/docs/cn/controller/design.md @@ -0,0 +1,205 @@ +# 背景 + +当前 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) + slaveAddressLength(4byte) + slaveAddress(50byte)` + +- Current state 代表当前的 HAConnectionState,也即 HANDSHAKE。 + +- Two falgs 是两个状态标志位,其中,isSyncFromLastFile 代表是否要从 Master 的最后一个文件开始复制,isAsyncLearner 代表该 Slave 是否是异步复制,并以 Learner 的形式接入 Master。 + +- slaveAddressLength 与 slaveAddress 代表了该 Slave 的地址,用于后续加入 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) diff --git a/docs/cn/controller/persistent_unique_broker_id.md b/docs/cn/controller/persistent_unique_broker_id.md new file mode 100644 index 00000000000..1d7a289fb7f --- /dev/null +++ b/docs/cn/controller/persistent_unique_broker_id.md @@ -0,0 +1,135 @@ +# 持久化的唯一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 | 无法正常上线 | 正常运行,可切换 | diff --git a/docs/cn/controller/quick_start.md b/docs/cn/controller/quick_start.md new file mode 100644 index 00000000000..5cc7d6d81fe --- /dev/null +++ b/docs/cn/controller/quick_start.md @@ -0,0 +1,200 @@ +# 自动主从切换快速开始 + +## 前言 + +![架构图](../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,在多机器部署的时候尤为重要。例子仅供参考需要根据实际情况进行修改调整。 diff --git a/docs/cn/design.md b/docs/cn/design.md index e2623c9c5b9..00b4de37905 100644 --- a/docs/cn/design.md +++ b/docs/cn/design.md @@ -10,11 +10,11 @@ #### 1.1 消息存储整体架构 消息存储架构图中主要有下面三个跟消息存储相关的文件构成。 -(1) CommitLog:消息主体以及元数据的存储主体,存储Producer端写入的消息主体内容,消息内容不是定长的。单个文件大小默认1G, 文件名长度为20位,左边补零,剩余为起始偏移量,比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始偏移量为1073741824,以此类推。消息主要是顺序写入日志文件,当文件满了,写入下一个文件; +(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; +(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索引。 +(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 页缓存与内存映射 @@ -32,6 +32,7 @@ (2) 异步刷盘:能够充分利用OS的PageCache的优势,只要消息写入PageCache即可将成功的ACK返回给Producer端。消息刷盘采用后台异步线程提交的方式进行,降低了读写延迟,提高了MQ的性能和吞吐量。 ### 2 通信机制 + RocketMQ消息队列集群主要包括NameServer、Broker(Master/Slave)、Producer、Consumer4个角色,基本通讯流程如下: (1) Broker启动后需要完成一次将自己注册至NameServer的操作;随后每隔30s时间定时向NameServer上报Topic路由信息。 @@ -42,7 +43,7 @@ RocketMQ消息队列集群主要包括NameServer、Broker(Master/Slave)、Produc (4) 消息消费者Consumer根据2)中获取的路由信息,并再完成客户端的负载均衡后,选择其中的某一个或者某几个消息队列来拉取消息并进行消费。 -从上面1)~3)中可以看出在消息生产者, Broker和NameServer之间都会发生通信(这里只说了MQ的部分通信),因此如何设计一个良好的网络通信模块在MQ中至关重要,它将决定RocketMQ集群整体的消息传输能力与最终的性能。 +从上面1)~3)中可以看出在消息生产者,Broker和NameServer之间都会发生通信(这里只说了MQ的部分通信),因此如何设计一个良好的网络通信模块在MQ中至关重要,它将决定RocketMQ集群整体的消息传输能力与最终的性能。 rocketmq-remoting 模块是 RocketMQ消息队列中负责网络通信的模块,它几乎被其他所有需要网络通信的模块(诸如rocketmq-client、rocketmq-broker、rocketmq-namesrv)所依赖和引用。为了实现客户端与服务器之间高效的数据请求与接收,RocketMQ消息队列自定义了通信协议并在Netty的基础之上扩展了通信模块。 #### 2.1 Remoting通信类结构 @@ -85,7 +86,7 @@ RocketMQ的RPC通信采用Netty组件作为底层通信库,同样也遵循了R ![](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”)。从入口到业务逻辑的几个步骤中线程池一直再增加,这跟每一步逻辑复杂性相关,越复杂,需要的并发通道越宽。 +从上面的框图中可以大致了解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”)。从入口到业务逻辑的几个步骤中线程池一直再增加,这跟每一步逻辑复杂性相关,越复杂,需要的并发通道越宽。 线程数 | 线程名 | 线程具体说明 --- | --- | --- @@ -107,7 +108,7 @@ RocketMQ分布式消息队列的消息过滤方式有别于其它MQ中间件, ### 4 负载均衡 RocketMQ中的负载均衡都在Client端完成,具体来说的话,主要可以分为Producer端发送消息时候的负载均衡和Consumer端订阅消息的负载均衡。 #### 4.1 Producer的负载均衡 -Producer端在发送消息的时候,会先根据Topic找到指定的TopicPublishInfo,在获取了TopicPublishInfo路由信息后,RocketMQ的客户端在默认方式下selectOneMessageQueue()方法会从TopicPublishInfo中的messageQueueList中选择一个队列(MessageQueue)进行发送消息。具体的容错策略均在MQFaultStrategy这个类中定义。这里有一个sendLatencyFaultEnable开关变量,如果开启,在随机递增取模的基础上,再过滤掉not available的Broker代理。所谓的"latencyFaultTolerance",是指对之前失败的,按一定的时间做退避。例如,如果上次请求的latency超过550Lms,就退避3000Lms;超过1000L,就退避60000L;如果关闭,采用随机递增取模的方式选择一个队列(MessageQueue)来发送消息,latencyFaultTolerance机制是实现消息发送高可用的核心关键所在。 +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消费。 @@ -131,13 +132,11 @@ Producer端在发送消息的时候,会先根据Topic找到指定的TopicPubli ![](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消息的请求。其中,可以重点对比下,RebalancePushImpl和RebalancePullImpl两个实现类的dispatchPullRequest()方法不同,RebalancePullImpl类里面的该方法为空,这样子也就回答了上一篇中最后的那道思考题了。 +最后,为过滤后的消息队列集合(mqSet)中的每个MessageQueue创建一个ProcessQueue对象并存入RebalanceImpl的processQueueTable队列中(其中调用RebalanceImpl实例的computePullFromWhere(MessageQueue mq)方法获取该MessageQueue对象的下一个进度消费值offset,随后填充至接下来要创建的pullRequest对象属性中),并创建拉取请求对象—pullRequest添加到拉取列表—pullRequestList中,最后执行dispatchPullRequest()方法,将Pull消息的请求对象PullRequest依次放入PullMessageService服务线程的阻塞队列pullRequestQueue中,待该服务线程取出后向Broker端发起Pull消息的请求。 消息消费队列在同一消费组不同消费者之间的负载均衡,其核心设计理念是在一个消息消费队列在同一时间只允许被同一消费组内的一个消费者消费,一个消息消费者能同时消费多个消息队列。 diff --git a/docs/cn/dledger/deploy_guide.md b/docs/cn/dledger/deploy_guide.md index c97e8dd8b42..2d04590e0b6 100644 --- a/docs/cn/dledger/deploy_guide.md +++ b/docs/cn/dledger/deploy_guide.md @@ -1,4 +1,5 @@ # Dledger集群搭建 +> 该模式为4.x的切换方式,建议采用 5.x [自动主从切换](../controller/design.md) --- ## 前言 该文档主要介绍如何部署自动容灾切换的 RocketMQ-on-DLedger Group。 @@ -19,7 +20,7 @@ RocketMQ-on-DLedger Group 是可以水平扩展的,也即可以部署任意多 | 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 | +| dLegerSelfId | 节点 id,必须属于 dLegerPeers 中的一个;同 Group 内各个节点要唯一 | n0 | | sendMessageThreadPoolNums | 发送线程个数,建议配置成 Cpu 核数 | 16 | 这里贴出 conf/dledger/broker-n0.conf 的配置举例。 diff --git a/docs/cn/dledger/quick_start.md b/docs/cn/dledger/quick_start.md index 48540c9dae1..8311395d993 100644 --- a/docs/cn/dledger/quick_start.md +++ b/docs/cn/dledger/quick_start.md @@ -1,4 +1,5 @@ # Dledger快速搭建 +> 该模式为4.x的切换方式,建议采用 5.x [自动主从切换](../controller/quick_start.md) --- ### 前言 该文档主要介绍如何快速构建和部署基于 DLedger 的可以自动容灾切换的 RocketMQ 集群。 @@ -10,33 +11,36 @@ #### 1.1 构建 DLedger -`git clone https://github.com/openmessaging/openmessaging-storage-dledger.git` - -`cd openmessaging-storage-dledger` - -`mvn clean install -DskipTests` +```shell +$ git clone https://github.com/openmessaging/dledger.git +$ cd dledger +$ mvn clean install -DskipTests +``` #### 1.2 构建 RocketMQ -`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` +```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. 快速部署 在构建成功后 -`cd distribution/target/apache-rocketmq` - -`sh bin/dledger/fast-try.sh start` +```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 运维命令查看集群状态。 -`sh bin/mqadmin clusterList -n 127.0.0.1:9876` +```shell +$ sh bin/mqadmin clusterList -n 127.0.0.1:9876 +``` 顺利的话,会看到如下内容: @@ -48,7 +52,9 @@ 关闭快速集群,可以执行: -`sh bin/dledger/fast-try.sh stop` +```shell +$ sh bin/dledger/fast-try.sh stop +``` 快速部署,默认配置在 conf/dledger 里面,默认的存储路径在 /tmp/rmqstore。 diff --git a/docs/cn/features.md b/docs/cn/features.md index e7b63864e9c..ab67683a27d 100644 --- a/docs/cn/features.md +++ b/docs/cn/features.md @@ -56,9 +56,9 @@ RocketMQ会为每个消费组都设置一个Topic名称为“%RETRY%+consumerGro ## 10 消息重投 生产者在发送消息时,同步消息失败会重投,异步消息有重试,oneway没有任何保证。消息重投保证消息尽可能发送成功、不丢失,但可能会造成消息重复,消息重复在RocketMQ中是无法避免的问题。消息重复在一般情况下不会发生,当出现消息量大、网络抖动,消息重复就会是大概率事件。另外,生产者主动重发、consumer负载变化也会导致重复消息。如下方法可以设置消息重试策略: -- retryTimesWhenSendFailed:同步发送失败重投次数,默认为2,因此生产者会最多尝试发送retryTimesWhenSendFailed + 1次。不会选择上次失败的broker,尝试向其他broker发送,最大程度保证消息不丢。超过重投次数,抛出异常,由客户端保证消息不丢。当出现RemotingException、MQClientException和部分MQBrokerException时会重投。 -- retryTimesWhenSendAsyncFailed:异步发送失败重试次数,异步重试不会选择其他broker,仅在同一个broker上做重试,不保证消息不丢。 -- retryAnotherBrokerWhenNotStoreOK:消息刷盘(主或备)超时或slave不可用(返回状态非SEND_OK),是否尝试发送到其他broker,默认false。十分重要消息可以开启。 +- retryTimesWhenSendFailed:同步发送失败重投次数,默认为2,因此生产者会最多尝试发送retryTimesWhenSendFailed + 1次。不会选择上次失败的broker,尝试向其他broker发送,最大程度保证消息不丢。超过重投次数,抛出异常,由客户端保证消息不丢。当出现RemotingException、MQClientException和部分MQBrokerException时会重投。 +- retryTimesWhenSendAsyncFailed:异步发送失败重试次数,异步重试不会选择其他broker,仅在同一个broker上做重试,不保证消息不丢。 +- retryAnotherBrokerWhenNotStoreOK:消息刷盘(主或备)超时或slave不可用(返回状态非SEND_OK),是否尝试发送到其他broker,默认false。十分重要消息可以开启。 ## 11 流量控制 生产者流控,因为broker处理能力达到瓶颈;消费者流控,因为消费能力达到瓶颈。 diff --git a/docs/cn/image/controller/controller_design_1.png b/docs/cn/image/controller/controller_design_1.png new file mode 100644 index 00000000000..fea825641f8 Binary files /dev/null and b/docs/cn/image/controller/controller_design_1.png differ diff --git a/docs/cn/image/controller/controller_design_2.png b/docs/cn/image/controller/controller_design_2.png new file mode 100644 index 00000000000..a82339472e9 Binary files /dev/null and b/docs/cn/image/controller/controller_design_2.png differ diff --git a/docs/cn/image/controller/controller_design_3.png b/docs/cn/image/controller/controller_design_3.png new file mode 100644 index 00000000000..8c475bcecf1 Binary files /dev/null and b/docs/cn/image/controller/controller_design_3.png differ diff --git a/docs/cn/image/controller/controller_design_4.png b/docs/cn/image/controller/controller_design_4.png new file mode 100644 index 00000000000..308b936279a Binary files /dev/null and b/docs/cn/image/controller/controller_design_4.png differ diff --git a/docs/cn/image/controller/controller_design_5.png b/docs/cn/image/controller/controller_design_5.png new file mode 100644 index 00000000000..01b33cab28d Binary files /dev/null and b/docs/cn/image/controller/controller_design_5.png differ diff --git a/docs/cn/image/controller/controller_design_6.png b/docs/cn/image/controller/controller_design_6.png new file mode 100644 index 00000000000..a909a70379a Binary files /dev/null and b/docs/cn/image/controller/controller_design_6.png differ diff --git a/docs/cn/image/controller/persistent_unique_broker_id/fail_apply_broker_id.png b/docs/cn/image/controller/persistent_unique_broker_id/fail_apply_broker_id.png new file mode 100644 index 00000000000..0689bd04b31 Binary files /dev/null and b/docs/cn/image/controller/persistent_unique_broker_id/fail_apply_broker_id.png differ diff --git a/docs/cn/image/controller/persistent_unique_broker_id/fail_create_metadata_file_and_delete_temp.png b/docs/cn/image/controller/persistent_unique_broker_id/fail_create_metadata_file_and_delete_temp.png new file mode 100644 index 00000000000..cee8ddfb2a5 Binary files /dev/null and b/docs/cn/image/controller/persistent_unique_broker_id/fail_create_metadata_file_and_delete_temp.png differ diff --git a/docs/cn/image/controller/persistent_unique_broker_id/fail_create_temp_metadata_file.png b/docs/cn/image/controller/persistent_unique_broker_id/fail_create_temp_metadata_file.png new file mode 100644 index 00000000000..32425d23604 Binary files /dev/null and b/docs/cn/image/controller/persistent_unique_broker_id/fail_create_temp_metadata_file.png differ diff --git a/docs/cn/image/controller/persistent_unique_broker_id/normal_restart.png b/docs/cn/image/controller/persistent_unique_broker_id/normal_restart.png new file mode 100644 index 00000000000..a454eada92a Binary files /dev/null and b/docs/cn/image/controller/persistent_unique_broker_id/normal_restart.png differ diff --git a/docs/cn/image/controller/persistent_unique_broker_id/register_process.png b/docs/cn/image/controller/persistent_unique_broker_id/register_process.png new file mode 100644 index 00000000000..200015765d9 Binary files /dev/null and b/docs/cn/image/controller/persistent_unique_broker_id/register_process.png differ diff --git a/docs/cn/image/controller/persistent_unique_broker_id/register_state_transfer.png b/docs/cn/image/controller/persistent_unique_broker_id/register_state_transfer.png new file mode 100644 index 00000000000..d6df0aa5d08 Binary files /dev/null and b/docs/cn/image/controller/persistent_unique_broker_id/register_state_transfer.png differ diff --git a/docs/cn/image/controller/quick-start/changemaster.png b/docs/cn/image/controller/quick-start/changemaster.png new file mode 100644 index 00000000000..6f486597008 Binary files /dev/null and b/docs/cn/image/controller/quick-start/changemaster.png differ diff --git a/docs/cn/image/controller/quick-start/controller.png b/docs/cn/image/controller/quick-start/controller.png new file mode 100644 index 00000000000..d7ffed6b80d Binary files /dev/null and b/docs/cn/image/controller/quick-start/controller.png differ diff --git a/docs/cn/image/controller/quick-start/epoch.png b/docs/cn/image/controller/quick-start/epoch.png new file mode 100644 index 00000000000..67dd768883c Binary files /dev/null and b/docs/cn/image/controller/quick-start/epoch.png differ diff --git a/docs/cn/image/controller/quick-start/syncstateset.png b/docs/cn/image/controller/quick-start/syncstateset.png new file mode 100644 index 00000000000..696a4c30826 Binary files /dev/null and b/docs/cn/image/controller/quick-start/syncstateset.png differ diff --git a/docs/cn/msg_trace/user_guide.md b/docs/cn/msg_trace/user_guide.md index b7807334403..d8314052bd9 100644 --- a/docs/cn/msg_trace/user_guide.md +++ b/docs/cn/msg_trace/user_guide.md @@ -5,7 +5,7 @@ | Producer端| Consumer端 | Broker端 | | --- | --- | --- | | 生产实例信息 | 消费实例信息 | 消息的Topic | -| 发送消息时间 | 投递时间,投递轮次  | 消息存储位置 | +| 发送消息时间 | 投递时间,投递轮次  | 消息存储位置 | | 消息是否发送成功 | 消息是否消费成功 | 消息的Key值 | | 发送耗时 | 消费耗时 | 消息的Tag值 | @@ -53,7 +53,7 @@ RocketMQ的消息轨迹特性支持两种存储轨迹数据的方式: 为了尽可能地减少用户业务系统使用RocketMQ消息轨迹特性的改造工作量,作者在设计时候采用对原来接口增加一个开关参数(**enableMsgTrace**)来实现消息轨迹是否开启;并新增一个自定义参数(**customizedTraceTopic**)来实现用户存储消息轨迹数据至自己创建的用户级Topic。 ### 4.1 发送消息时开启消息轨迹 -``` +```java DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true); producer.setNamesrvAddr("XX.XX.XX.XX1"); producer.start(); @@ -73,7 +73,7 @@ RocketMQ的消息轨迹特性支持两种存储轨迹数据的方式: ``` ### 4.2 订阅消息时开启消息轨迹 -``` +```java DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_JODIE_1",true); consumer.subscribe("TopicTest", "*"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); diff --git a/docs/cn/operation.md b/docs/cn/operation.md index e5275887b5f..4310da570b8 100644 --- a/docs/cn/operation.md +++ b/docs/cn/operation.md @@ -6,7 +6,7 @@ #### 1.1 单Master模式 -这种方式风险较大,一旦Broker重启或者宕机时,会导致整个服务不可用。不建议线上环境使用,可以用于本地测试。 +这种方式风险较大,一旦Broker重启或者宕机时,会导致整个服务不可用。不建议线上环境使用,可以用于本地测试。 ##### 1)启动 NameServer @@ -137,6 +137,16 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker 以上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管理工具 > 注意: @@ -559,13 +569,21 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker NameServer 服务地址,格式 ip:port - wipeWritePerm - 从NameServer上清除 Broker写权限 -b BrokerName + + -n + NameServer 服务地址,格式 ip:port + + + -h + 打印帮助 + addWritePerm @@ -573,7 +591,7 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker border-top:none;width:65pt'>从NameServer上添加 Broker写权限 -b BrokerName - + -n NameServer 服务地址,格式 ip:port @@ -602,6 +620,26 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker -c 集群名称 + + deleteExpiredCommitLog + 清理Broker上过期的CommitLog文件,Broker最多会执行20次删除操作,每次最多删除10个文件 + -n + NameServer 服务地址,格式 ip:port + + + -h + 打印帮助 + + + -b + Broker 地址,地址为ip:port + + + -c + 集群名称 + cleanUnusedTopic @@ -1358,13 +1396,13 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker 解决方案:rocketmq默认策略是从消息队列尾部,即跳过历史消息。如果想消费历史消息,则需要设置:`org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#setConsumeFromWhere`。常用的有以下三种配置: -- 默认配置,一个新的订阅组第一次启动从队列的最后位置开始消费,后续再启动接着上次消费的进度开始消费,即跳过历史消息; +- 默认配置,一个新的订阅组第一次启动从队列的最后位置开始消费,后续再启动接着上次消费的进度开始消费,即跳过历史消息; ```java consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); ``` -- 一个新的订阅组第一次启动从队列的最前位置开始消费,后续再启动接着上次消费的进度开始消费,即消费Broker未过期的历史消息; +- 一个新的订阅组第一次启动从队列的最前位置开始消费,后续再启动接着上次消费的进度开始消费,即消费Broker未过期的历史消息; ```java consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); diff --git a/docs/cn/proxy/deploy_guide.md b/docs/cn/proxy/deploy_guide.md new file mode 100644 index 00000000000..5ecc1386b61 --- /dev/null +++ b/docs/cn/proxy/deploy_guide.md @@ -0,0 +1,35 @@ +# 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`指定的地址运行。 diff --git "a/docs/cn/statictopic/RocketMQ_Static_Topic_Logic_Queue_\350\256\276\350\256\241.md" "b/docs/cn/statictopic/RocketMQ_Static_Topic_Logic_Queue_\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..784a83f929f --- /dev/null +++ "b/docs/cn/statictopic/RocketMQ_Static_Topic_Logic_Queue_\350\256\276\350\256\241.md" @@ -0,0 +1,503 @@ +### 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目录。 + + + + + diff --git a/docs/cn/statictopic/The_Scope_Of_Static_Topic.md b/docs/cn/statictopic/The_Scope_Of_Static_Topic.md new file mode 100644 index 00000000000..886e33e2f4c --- /dev/null +++ b/docs/cn/statictopic/The_Scope_Of_Static_Topic.md @@ -0,0 +1,116 @@ +### 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』 + + + + + + + + diff --git a/docs/en/CLITools.md b/docs/en/CLITools.md index 596e84e7703..208ae44f35d 100644 --- a/docs/en/CLITools.md +++ b/docs/en/CLITools.md @@ -184,15 +184,15 @@ Before introducing the mqadmin management tool, the following points need to be -t - topic,key + topic, key -v - orderConf,value + orderConf, value -m - method,available values include get, put, delete + method, available values include get, put, delete -i - Print interval,unit basis is seconds + Print interval, unit basis is seconds 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 + amount, total number per probe, RT = Total time/amount -s - Message size,unit basis is B + Message size, unit basis is B -c @@ -419,9 +419,9 @@ Before introducing the mqadmin management tool, the following points need to be Service address used to specify nameServer and formatted as ip:port - wipeWritePerm - Clear write permissions for broker from nameServer -b Declare the BrokerName @@ -446,7 +446,27 @@ Before introducing the mqadmin management tool, the following points need to be cleanExpiredCQ Clean up expired consume Queue on broker,An expired queue may be generated if the number of columns is reduced manually + border-top:none;width:65pt'>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 @@ -466,7 +486,7 @@ Before introducing the mqadmin management tool, the following points need to be 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 + border-top:none;width:65pt'>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 @@ -496,11 +516,11 @@ Before introducing the mqadmin management tool, the following points need to be -b - brokerName,note that this is not broker's address + brokerName, note that this is not broker's address -s - Message size,the unit of account is B + Message size, the unit of account is B -c @@ -604,7 +624,7 @@ Before introducing the mqadmin management tool, the following points need to be -i - uniqe msg id + unique msg id -g @@ -660,7 +680,7 @@ Before introducing the mqadmin management tool, the following points need to be -p - body,message body + body, message body -k @@ -740,11 +760,11 @@ Before introducing the mqadmin management tool, the following points need to be -c - Character set,for example UTF-8 + Character set, for example UTF-8 -s - subExpress,filter expression + subExpress, filter expression -b @@ -784,11 +804,11 @@ Before introducing the mqadmin management tool, the following points need to be -c - Character set,for example UTF-8 + Character set, for example UTF-8 -s - subExpress,filter expression + subExpress, filter expression -b diff --git a/docs/en/Concept.md b/docs/en/Concept.md index 75f084e1723..03f23842934 100644 --- a/docs/en/Concept.md +++ b/docs/en/Concept.md @@ -4,7 +4,7 @@ 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 seperate queues. The consumer group consists of multiple consumer instances. +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 @@ -18,7 +18,8 @@ The Name Server serves as the provider of routing service. The producer or the c ## 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, Under this high real-time performance mode, it will push the message to the consumer actively when the Broker receives the data. +A type of Consumer, the application do not invoke the consumer pull message method to pull messages, instead the client invoke pull message method itself. At the user level it seems like brokers +push to consumer when new messages arrived. ## 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 @@ -38,4 +39,4 @@ Under the Strictly Ordered Message mode, all messages received by the consumers 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 in terms of 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-topic" by using tag in order to achieve better expansibility. +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 in terms of 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-topic" by using tag in order to achieve better expandability. diff --git a/docs/en/Configuration_Client.md b/docs/en/Configuration_Client.md index 7aabaa862a5..4d999b2feda 100644 --- a/docs/en/Configuration_Client.md +++ b/docs/en/Configuration_Client.md @@ -18,7 +18,7 @@ consumer.setNamesrvAddr("192.168.0.1:9876;192.168.0.2:9876"); ```text -Drocketmq.namesrv.addr=192.168.0.1:9876;192.168.0.2:9876 ``` -- Specified ```Name Server``` address in the envionment variables +- Specified ```Name Server``` address in the environment variables ```text export NAMESRV_ADDR=192.168.0.1:9876;192.168.0.2:9876 @@ -63,7 +63,7 @@ HTTP static server addressing is recommended, because it is simple client deploy | compressMsgBodyOverHowmuch | 4096 | The message Body begins to compress beyond the size(the Consumer gets the message automatically unzipped.), unit of byte| | retryAnotherBrokerWhenNotStoreOK | FALSE | If send message and return sendResult but sendStatus!=SEND_OK, Whether to resend | | retryTimesWhenSendFailed | 2 | If send message failed, maximum number of retries, this parameter only works for synchronous send mode| -| maxMessageSize | 4MB | Client limit message size, over it may error. Server also limit so need to work with server | +| maxMessageSize | 4MB | Client limit message body size, over it may error. Server also limit so need to work with server | | transactionCheckListener | | The transaction message looks back to the listener, if you want send transaction message, you must setup this | checkThreadPoolMinSize | 1 | Minimum of thread in thread pool when Broker look back Producer transaction status | | checkThreadPoolMaxSize | 1 | Maximum of thread in thread pool when Broker look back Producer transaction status | diff --git a/docs/en/Configuration_System.md b/docs/en/Configuration_System.md index ab5f121ae81..e263b0c4c28 100644 --- a/docs/en/Configuration_System.md +++ b/docs/en/Configuration_System.md @@ -59,7 +59,7 @@ There is a os.sh script that lists a lot of kernel parameters in folder bin whic -- **vm.swappiness**, define how aggressive the kernel will swap memory pages. Higher values will increase agressiveness, lower values decrease the amount of swap. 10 is recommended for this value to avoid swap latency. +- **vm.swappiness**, define how aggressive the kernel will swap memory pages. Higher values will increase aggressiveness, lower values decrease the amount of swap. 10 is recommended for this value to avoid swap latency. diff --git a/docs/en/Configuration_TLS.md b/docs/en/Configuration_TLS.md new file mode 100644 index 00000000000..445d186d27c --- /dev/null +++ b/docs/en/Configuration_TLS.md @@ -0,0 +1,123 @@ +# TLS Configuration +This section introduce TLS configuration in RocketMQ. + +## 1 Generate Certification Files +User can generate certification files using OpenSSL. 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 linke 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(); + } +} +``` diff --git a/docs/en/Deployment.md b/docs/en/Deployment.md index eadfec53d5a..5dc93488a77 100644 --- a/docs/en/Deployment.md +++ b/docs/en/Deployment.md @@ -73,7 +73,7 @@ The boot command shown above is used in the case of a single NameServer.For clus ### 3 Multiple Master And Multiple Slave Mode-Asynchronous replication -Each master node configures more thran 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: +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. diff --git a/docs/en/Design_Filter.md b/docs/en/Design_Filter.md index 28eb59948ec..7ccb94fa200 100644 --- a/docs/en/Design_Filter.md +++ b/docs/en/Design_Filter.md @@ -1,5 +1,5 @@ # 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 Consomer 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. +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) diff --git a/docs/en/Design_LoadBlancing.md b/docs/en/Design_LoadBlancing.md index 971e8b5dd45..86c47b16536 100644 --- a/docs/en/Design_LoadBlancing.md +++ b/docs/en/Design_LoadBlancing.md @@ -1,9 +1,9 @@ ## 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 Constumer side when subscribing messages. +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 agent of 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 3000 Lms; if it exceeds 1000L, it will evade 60000 L; 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. +Here is a sendLatencyFaultEnable switch variable, which, if turned on, filters out the Broker agent of 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. diff --git a/docs/en/Design_Remoting.md b/docs/en/Design_Remoting.md index 95eb8520cb3..46e001a1dc5 100644 --- a/docs/en/Design_Remoting.md +++ b/docs/en/Design_Remoting.md @@ -19,7 +19,7 @@ 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 reqeustId, the different request identification codes on the same connection correspond to those in the response message| The response returns directly without modification +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 @@ -28,7 +28,7 @@ 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; +(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; @@ -45,6 +45,6 @@ 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 | bussiness processor thread pool +M2 | RemotingExecutorThread_%d | business processor thread pool diff --git a/docs/en/Design_Store.md b/docs/en/Design_Store.md index cbc661305da..747c98e6fb6 100644 --- a/docs/en/Design_Store.md +++ b/docs/en/Design_Store.md @@ -14,7 +14,7 @@ Message storage is the most complicated and important part of RocketMQ. This sec 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. +* `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. diff --git a/docs/en/Example_Batch.md b/docs/en/Example_Batch.md index 06461bb51d7..e62f4638111 100644 --- a/docs/en/Example_Batch.md +++ b/docs/en/Example_Batch.md @@ -26,10 +26,12 @@ public class ListSplitter implements Iterator> { public ListSplitter(List messages) { this.messages = messages; } - @Override public boolean hasNext() { + @Override + public boolean hasNext() { return currIndex < messages.size(); } - @Override public List next() { + @Override + public List next() { int startIndex = getStartIndex(); int nextIndex = startIndex; int totalSize = 0; diff --git a/docs/en/Example_Compaction_Topic.md b/docs/en/Example_Compaction_Topic.md new file mode 100644 index 00000000000..ed5528686f5 --- /dev/null +++ b/docs/en/Example_Compaction_Topic.md @@ -0,0 +1,68 @@ +# 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 +``` diff --git a/docs/en/Example_CreateTopic.md b/docs/en/Example_CreateTopic.md new file mode 100644 index 00000000000..c3fb5a68cd0 --- /dev/null +++ b/docs/en/Example_CreateTopic.md @@ -0,0 +1,26 @@ +# 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 +``` diff --git a/docs/en/Example_Delay.md b/docs/en/Example_Delay.md index fe41aed92ba..a136d25f314 100644 --- a/docs/en/Example_Delay.md +++ b/docs/en/Example_Delay.md @@ -15,6 +15,8 @@ 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 @@ -46,6 +48,8 @@ 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; diff --git a/docs/en/Example_Filter.md b/docs/en/Example_Filter.md index 8c95a911061..768692d9dff 100644 --- a/docs/en/Example_Filter.md +++ b/docs/en/Example_Filter.md @@ -8,7 +8,7 @@ DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_EXAMPLE"); consumer.subscribe("TOPIC", "TAGA || TAGB || TAGC"); ``` -The consumer will recieve 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. +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: ``` @@ -73,7 +73,7 @@ Use `MessageSelector.bySql` to select messages through SQL when consuming. ```java DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4"); -// only subsribe messages have property a, also a >=0 and a <= 3 +// 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 diff --git a/docs/en/Example_OpenMessaging.md b/docs/en/Example_OpenMessaging.md index 026e76e902d..a79fd10f2c7 100644 --- a/docs/en/Example_OpenMessaging.md +++ b/docs/en/Example_OpenMessaging.md @@ -1,5 +1,5 @@ # 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, ecommerce, 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. +[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. diff --git a/docs/en/FAQ.md b/docs/en/FAQ.md index a08315cb153..dac53ecbfd4 100644 --- a/docs/en/FAQ.md +++ b/docs/en/FAQ.md @@ -70,7 +70,7 @@ consumer.setConsumeThreadMax(20); ### 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. +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. @@ -88,11 +88,11 @@ Messages can no longer be sent to this broker set, but if you have another broke  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, comsumer group consumes from master. +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, comsumer group consumes from master. +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? @@ -104,6 +104,6 @@ This happens when you are trying to send messages to a topic whose routing info  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 permssion is 6(rw-), or at least 2(-w-). + 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. diff --git a/docs/en/Feature.md b/docs/en/Feature.md index 5945588de4d..c51b965f25c 100644 --- a/docs/en/Feature.md +++ b/docs/en/Feature.md @@ -33,7 +33,7 @@ In the four cases of 1), 2), 3), and 4) where the hardware resource can be recov 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 failured, 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. +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. @@ -83,4 +83,9 @@ 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. \ No newline at end of file +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. \ No newline at end of file diff --git a/docs/en/Operations_Consumer.md b/docs/en/Operations_Consumer.md index 84047dfc929..8944be16d45 100644 --- a/docs/en/Operations_Consumer.md +++ b/docs/en/Operations_Consumer.md @@ -4,7 +4,7 @@ ### 1 Consumption process idempotent -RocketMQ cannot avoid 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) +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 @@ -27,7 +27,7 @@ When a message is accumulated, if the consumption speed cannot keep up with the public ConsumeConcurrentlyStatus consumeMessage( List msgs, ConsumeConcurrentlyContext context){ - long offest = msgs.get(0).getQueueOffset(); + long offset = msgs.get(0).getQueueOffset(); String maxOffset = msgs.get(0).getProperty(Message.PROPERTY_MAX_OFFSET); long diff = Long.parseLong(maxOffset) - offset; @@ -36,7 +36,7 @@ public ConsumeConcurrentlyStatus consumeMessage( return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } //TODO Normal consumption process - return ConcumeConcurrentlyStatus.CONSUME_SUCCESS; + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } ``` diff --git a/docs/en/Operations_Producer.md b/docs/en/Operations_Producer.md index 68ef4a69210..c7f0d5711aa 100644 --- a/docs/en/Operations_Producer.md +++ b/docs/en/Operations_Producer.md @@ -33,7 +33,7 @@ The send method of Producer can be retried, the retry process is illustrated be 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 Asyn-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. +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: diff --git a/docs/en/Operations_Trace.md b/docs/en/Operations_Trace.md index 5ed9c8d0863..74c2cde1af2 100644 --- a/docs/en/Operations_Trace.md +++ b/docs/en/Operations_Trace.md @@ -37,7 +37,7 @@ namesrvAddr=XX.XX.XX.XX:9876 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 RockeMQ cluster, one of which is defined as the server on which message trace data is stored. +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 &` @@ -46,10 +46,10 @@ For scenarios with large amount of trace message data , one of the Broker nodes 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)。 +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. +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. @@ -92,10 +92,10 @@ In order to reduce as much as possible the transformation work of RocketMQ messa ``` ### 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 subscriving messages above. +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; + ##Where Topic_test11111 needs to be pre-created by the user to save the message trace; DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true,"Topic_test11111"); ...... diff --git a/docs/en/QuorumACK.md b/docs/en/QuorumACK.md new file mode 100644 index 00000000000..bd8c565abe0 --- /dev/null +++ b/docs/en/QuorumACK.md @@ -0,0 +1,73 @@ +# 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) \ No newline at end of file diff --git a/docs/en/README.md b/docs/en/README.md index b24d2403fdf..2a096c3340a 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -1,40 +1,43 @@ Apache RocketMQ Developer Guide -------- -##### This guide helps develpers understand and use Apache RocketMQ quickly. +##### This guide helps developers to understand and use Apache RocketMQ quickly. ### 1. Concepts & Features -- [Concept](Concept.md):introduce basic concepts in RocketMQ. +- [Concept](Concept.md): introduce basic concepts in RocketMQ. -- [Feature](Feature.md):introduce functional features of RocketMQ's implementations. +- [Feature](Feature.md): introduce functional features of RocketMQ's implementations. ### 2. Architecture Design -- [Architecture](architecture.md):introduce RocketMQ's deployment and technical architecture. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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) diff --git a/docs/en/Troubleshoopting.md b/docs/en/Troubleshoopting.md index 02a245ed04f..ee4adab8cfb 100644 --- a/docs/en/Troubleshoopting.md +++ b/docs/en/Troubleshoopting.md @@ -2,7 +2,7 @@ ## 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: +> 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 @@ -72,5 +72,5 @@ After sending message with RocketMQ, you will usually see the following log: 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 concating "IP address + port" and "CommitLog's physical offset address"), and offsetMsgId is the messageId used to query in the RocketMQ console. +- 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. diff --git a/docs/en/acl/Operations_ACL.md b/docs/en/acl/Operations_ACL.md index b29f63e3d97..0651ea8b060 100644 --- a/docs/en/acl/Operations_ACL.md +++ b/docs/en/acl/Operations_ACL.md @@ -73,4 +73,4 @@ The verification of the required permissions of the user requires attention to t (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 RocketrMQ'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. +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. diff --git a/docs/en/architecture.md b/docs/en/architecture.md index 18e1cc0023b..863a62200a2 100644 --- a/docs/en/architecture.md +++ b/docs/en/architecture.md @@ -6,18 +6,18 @@ 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. +- 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. +- 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. +- 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. +- 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) diff --git a/docs/en/best_practice.md b/docs/en/best_practice.md index e9d0ee4e307..da279e297ed 100755 --- a/docs/en/best_practice.md +++ b/docs/en/best_practice.md @@ -43,7 +43,7 @@ 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 timout(default is 5s +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** @@ -72,7 +72,7 @@ Thirdly, the producer is a virtual machine with low reliability, which is not su 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: +Typically, this is the process by which messages are sent: - Client send request to server - Server process request diff --git a/docs/en/client/java/API_Reference_DefaultMQProducer.md b/docs/en/client/java/API_Reference_DefaultMQProducer.md index 152f45dca21..e32422b6f12 100644 --- a/docs/en/client/java/API_Reference_DefaultMQProducer.md +++ b/docs/en/client/java/API_Reference_DefaultMQProducer.md @@ -6,7 +6,7 @@ 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/)。 +>`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 { @@ -54,12 +54,12 @@ public class Producer { |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 | +|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. | diff --git a/docs/en/controller/deploy.md b/docs/en/controller/deploy.md new file mode 100644 index 00000000000..84849510ef3 --- /dev/null +++ b/docs/en/controller/deploy.md @@ -0,0 +1,137 @@ +# 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. \ No newline at end of file diff --git a/docs/en/controller/design.md b/docs/en/controller/design.md new file mode 100644 index 00000000000..ba2de58af14 --- /dev/null +++ b/docs/en/controller/design.md @@ -0,0 +1,192 @@ +# 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) + slaveAddressLength(4byte) + slaveAddress(50byte)` + +- `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. + +- `slaveAddressLength` and `slaveAddress` represent the address 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) diff --git a/docs/en/controller/persistent_unique_broker_id.md b/docs/en/controller/persistent_unique_broker_id.md new file mode 100644 index 00000000000..cd076578543 --- /dev/null +++ b/docs/en/controller/persistent_unique_broker_id.md @@ -0,0 +1,129 @@ +# 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. | \ No newline at end of file diff --git a/docs/en/controller/quick_start.md b/docs/en/controller/quick_start.md new file mode 100644 index 00000000000..e7997213b0f --- /dev/null +++ b/docs/en/controller/quick_start.md @@ -0,0 +1,200 @@ +# 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. diff --git a/docs/en/design.md b/docs/en/design.md index 4bd788aa99e..c919862b7bc 100644 --- a/docs/en/design.md +++ b/docs/en/design.md @@ -5,7 +5,7 @@ ![](../cn/image/rocketmq_design_1.png) -#### 1.1 The Architecure of Message Store +#### 1.1 The Architecture of Message Store #### 1.2 PageCache and Memory-Map(Mmap) diff --git a/docs/en/dledger/deploy_guide.md b/docs/en/dledger/deploy_guide.md index 55509498dd5..06cf333a69a 100644 --- a/docs/en/dledger/deploy_guide.md +++ b/docs/en/dledger/deploy_guide.md @@ -1,7 +1,7 @@ # Dledger cluster deployment --- ## preface -This document introduces how to deploy auto failover RocketMQ-on-DLedger Group。 +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. @@ -12,7 +12,7 @@ RocketMQ-on-DLedger Group can scale up horizontal, that is, can deploy any Rocke #### 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: +key configuration items: | name | meaning | example | | --- | --- | --- | diff --git a/docs/en/dledger/quick_start.md b/docs/en/dledger/quick_start.md index f947e166d51..dfc7894af56 100644 --- a/docs/en/dledger/quick_start.md +++ b/docs/en/dledger/quick_start.md @@ -3,54 +3,59 @@ ### 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)。 +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 -`git clone https://github.com/openmessaging/openmessaging-storage-dledger.git` - -`cd openmessaging-storage-dledger` - -`mvn clean install -DskipTests` +```shell +$ git clone https://github.com/openmessaging/dledger.git +$ cd dledger +$ mvn clean install -DskipTests +``` #### 1.2 Build RocketMQ -`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` +```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 -`cd distribution/target/apache-rocketmq` - -`sh bin/dledger/fast-try.sh start` +```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. -`sh bin/mqadmin clusterList -n 127.0.0.1:9876` +```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) +(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: -`sh bin/dledger/fast-try.sh stop` +```shell +$ sh bin/dledger/fast-try.sh stop +``` -Quick deployment, default configuration is in directory conf/dledger,default storage path is /tmp/rmqstore. +Quick deployment, default configuration is in directory conf/dledger, default storage path is /tmp/rmqstore. ### 3. Failover diff --git a/docs/en/image/controller/controller_design_1.png b/docs/en/image/controller/controller_design_1.png new file mode 100644 index 00000000000..fea825641f8 Binary files /dev/null and b/docs/en/image/controller/controller_design_1.png differ diff --git a/docs/en/image/controller/controller_design_2.png b/docs/en/image/controller/controller_design_2.png new file mode 100644 index 00000000000..a82339472e9 Binary files /dev/null and b/docs/en/image/controller/controller_design_2.png differ diff --git a/docs/en/image/controller/controller_design_3.png b/docs/en/image/controller/controller_design_3.png new file mode 100644 index 00000000000..8c475bcecf1 Binary files /dev/null and b/docs/en/image/controller/controller_design_3.png differ diff --git a/docs/en/image/controller/controller_design_4.png b/docs/en/image/controller/controller_design_4.png new file mode 100644 index 00000000000..308b936279a Binary files /dev/null and b/docs/en/image/controller/controller_design_4.png differ diff --git a/docs/en/image/controller/controller_design_5.png b/docs/en/image/controller/controller_design_5.png new file mode 100644 index 00000000000..01b33cab28d Binary files /dev/null and b/docs/en/image/controller/controller_design_5.png differ diff --git a/docs/en/image/controller/controller_design_6.png b/docs/en/image/controller/controller_design_6.png new file mode 100644 index 00000000000..a909a70379a Binary files /dev/null and b/docs/en/image/controller/controller_design_6.png differ diff --git a/docs/en/image/controller/persistent_unique_broker_id/fail_apply_broker_id.png b/docs/en/image/controller/persistent_unique_broker_id/fail_apply_broker_id.png new file mode 100644 index 00000000000..0689bd04b31 Binary files /dev/null and b/docs/en/image/controller/persistent_unique_broker_id/fail_apply_broker_id.png differ diff --git a/docs/en/image/controller/persistent_unique_broker_id/fail_create_metadata_file_and_delete_temp.png b/docs/en/image/controller/persistent_unique_broker_id/fail_create_metadata_file_and_delete_temp.png new file mode 100644 index 00000000000..cee8ddfb2a5 Binary files /dev/null and b/docs/en/image/controller/persistent_unique_broker_id/fail_create_metadata_file_and_delete_temp.png differ diff --git a/docs/en/image/controller/persistent_unique_broker_id/fail_create_temp_metadata_file.png b/docs/en/image/controller/persistent_unique_broker_id/fail_create_temp_metadata_file.png new file mode 100644 index 00000000000..32425d23604 Binary files /dev/null and b/docs/en/image/controller/persistent_unique_broker_id/fail_create_temp_metadata_file.png differ diff --git a/docs/en/image/controller/persistent_unique_broker_id/normal_restart.png b/docs/en/image/controller/persistent_unique_broker_id/normal_restart.png new file mode 100644 index 00000000000..a454eada92a Binary files /dev/null and b/docs/en/image/controller/persistent_unique_broker_id/normal_restart.png differ diff --git a/docs/en/image/controller/persistent_unique_broker_id/register_process.png b/docs/en/image/controller/persistent_unique_broker_id/register_process.png new file mode 100644 index 00000000000..200015765d9 Binary files /dev/null and b/docs/en/image/controller/persistent_unique_broker_id/register_process.png differ diff --git a/docs/en/image/controller/persistent_unique_broker_id/register_state_transfer.png b/docs/en/image/controller/persistent_unique_broker_id/register_state_transfer.png new file mode 100644 index 00000000000..d6df0aa5d08 Binary files /dev/null and b/docs/en/image/controller/persistent_unique_broker_id/register_state_transfer.png differ diff --git a/docs/en/image/controller/quick-start/changemaster.png b/docs/en/image/controller/quick-start/changemaster.png new file mode 100644 index 00000000000..6f486597008 Binary files /dev/null and b/docs/en/image/controller/quick-start/changemaster.png differ diff --git a/docs/en/image/controller/quick-start/controller.png b/docs/en/image/controller/quick-start/controller.png new file mode 100644 index 00000000000..d7ffed6b80d Binary files /dev/null and b/docs/en/image/controller/quick-start/controller.png differ diff --git a/docs/en/image/controller/quick-start/epoch.png b/docs/en/image/controller/quick-start/epoch.png new file mode 100644 index 00000000000..67dd768883c Binary files /dev/null and b/docs/en/image/controller/quick-start/epoch.png differ diff --git a/docs/en/image/controller/quick-start/syncstateset.png b/docs/en/image/controller/quick-start/syncstateset.png new file mode 100644 index 00000000000..696a4c30826 Binary files /dev/null and b/docs/en/image/controller/quick-start/syncstateset.png differ diff --git a/docs/en/images/rocketmq_proxy_cluster_mode.png b/docs/en/images/rocketmq_proxy_cluster_mode.png new file mode 100644 index 00000000000..1b4eb5eb31b Binary files /dev/null and b/docs/en/images/rocketmq_proxy_cluster_mode.png differ diff --git a/docs/en/images/rocketmq_proxy_local_mode.png b/docs/en/images/rocketmq_proxy_local_mode.png new file mode 100644 index 00000000000..12e6354a8e3 Binary files /dev/null and b/docs/en/images/rocketmq_proxy_local_mode.png differ diff --git a/docs/en/index.md b/docs/en/index.md deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/docs/en/msg_trace/user_guide.md b/docs/en/msg_trace/user_guide.md index 9573056d132..64c4b2bb434 100644 --- a/docs/en/msg_trace/user_guide.md +++ b/docs/en/msg_trace/user_guide.md @@ -104,7 +104,7 @@ Adjusting instantiation of DefaultMQProducer and DefaultMQPushConsumer as follow ### 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 meesgae content" +./mqadmin sendMessage -m true --topic some-topic-name -n 127.0.0.1:9876 -p "your message content" ``` - query trace ```shell diff --git a/docs/en/operation.md b/docs/en/operation.md index 1b8375fb6c3..a6b707bab13 100644 --- a/docs/en/operation.md +++ b/docs/en/operation.md @@ -26,7 +26,7 @@ The Name Server boot success... $ 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 +$ tail -f ~/logs/rocketmqlogs/broker.log The broker[broker-a, 192.169.1.2:10911] boot success... ``` @@ -34,9 +34,9 @@ The broker[broker-a, 192.169.1.2:10911] boot success... 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. +- 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. +- 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 @@ -54,10 +54,10 @@ 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 +### 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 +### 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 & ... @@ -104,7 +104,7 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broke 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 lantancy even if Master is down, service available and data available is very high; +- 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. @@ -139,9 +139,9 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke ### 2 mqadmin management tool -> Attentions: +> Attentions: > -> 1. execute command:`./mqadmin {command} {args}` +> 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 @@ -158,7 +158,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke name meaning command items - explaination + explanation -c - cluster name, whic cluster that topic belongs to(query cluster info by clusterList) + cluster name, which cluster that topic belongs to(query cluster info by clusterList) -h- @@ -226,7 +226,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke -c - return topic list only if do not contains -c, if containis -c, it will return cluster name, topic name, consumer group name + return topic list only if do not contains -c, if contains -c, it will return cluster name, topic name, consumer group name -n @@ -306,7 +306,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke -c - cluster name, which topic belongs to(query cluster info by clusterList), if do not have -b, execute comman an all brokers. + cluster name, which topic belongs to(query cluster info by clusterList), if do not have -b, execute command on all brokers. -i - ipList, seperate by comma, calculate which topic queue that ips will load. + ipList, separate by comma, calculate which topic queue that ips will load. 名称 meaning command items - explaination + explanation clusterRT send message to detect each cluster's Broker RT. Message will be sent to ${BrokerName} Topic。 + border-top:none;width:131pt'>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 @@ -429,7 +429,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke -p - whether print format log, splitted by |, default is not print + whether print format log, split by |, default is not print -h @@ -461,7 +461,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke 名称 meaning command items - explaination + explanation get Broker's statistics info, running status(including whatever you want). -b - Broker address, fomat isip:port + Broker address, format is ip:port -h @@ -511,9 +511,9 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke brokerConsumeStats Broker's consumer info, including Consume Offset, Broker Offset, Diff, Timestamp that ordered by essage Queue + border-top:none;width:65pt'>Broker's consumer info, including Consume Offset, Broker Offset, Diff, Timestamp that ordered by message Queue -b - Broker address, fomat isip:port + Broker address, format is ip:port -t @@ -525,7 +525,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke -o - whether is sequencial topic, generally false + whether is sequential topic, generally false -h @@ -541,7 +541,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke get Broker's config -b - Broker address, fomat isip:port + Broker address, format is ip:port -n @@ -553,7 +553,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke revoke broker's write authority from NameServer. -b - Broker address, fomat isip:port + Broker address, format is ip:port -n @@ -577,7 +577,27 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke -b - Broker address, fomat isip:port + 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 @@ -587,7 +607,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke cleanUnusedTopic clean Broker's unused Topic that deleted mannually to release memory that Topic's Consume Queue occupied. + border-top:none;width:65pt'>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 @@ -597,7 +617,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke -b - Broker address, fomat isip:port + Broker address, format is ip:port -c @@ -641,7 +661,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke 名称 meaning command items - explaination + explanation -i - uniqe msg id + unique msg id -g @@ -739,7 +759,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke checkMsgSendRT detect RT of sending a message to a topic, similiar to clusterRT + border-top:none;width:65pt'>detect RT of sending a message to a topic, similar to clusterRT -h print help info @@ -799,7 +819,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke consumeMessage consume message. Differert consume logic depends on offset, start & end timestamp, message queue, please refer to ConsumeMessageCommand for details. + border-top:none;width:65pt'>consume message. Different consume logic depends on offset, start & end timestamp, message queue, please refer to ConsumeMessageCommand for details. -h print help info @@ -825,7 +845,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke -g - consumer gropu + consumer group -s @@ -865,7 +885,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke -b - timestap at start, refer to -h to get format + timestamp at start, refer to -h to get format -e @@ -931,7 +951,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke resetOffsetByTime reset offset by timestamp, Broker and consumer will all be reseted + border-top:none;width:65pt'>reset offset by timestamp, Broker and consumer will all be reset -h print help info @@ -973,7 +993,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke name meaning command items - explaination + explanation -w - If broker consume from slave, whic slave node depends on this config that configed by BrokerId, eg: 1. + If broker consume from slave, which slave node depends on this config that defined by BrokerId, eg: 1. -a @@ -1143,11 +1163,11 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke name meaning command items - explaination + explanation consumerConnec tion + height:89.0pt;border-top:none;width:65pt'>consumerConnection query Consumer's connection -g @@ -1163,7 +1183,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke producerConnec tion + height:106.0pt;border-top:none;width:65pt'>producerConnection query Producer's connection -g @@ -1197,7 +1217,7 @@ The above Broker matches Slave by specifying the same BrokerName, Master's Broke name meaning command items - explaination + explanation name meaning command items - explaination + explanation question description:execute mqadmin occur below exception after deploy RocketMQ cluster. +> question description: execute mqadmin occur below exception after deploy RocketMQ cluster. > > ```java > org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to failed @@ -1325,7 +1345,7 @@ Solution: execute command `export NAMESRV_ADDR=ip:9876` (ip is NameServer's ip a > 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。 +> 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. @@ -1342,7 +1362,7 @@ Solution: rocketmq's default policy is consume from latest, that is skip oldest consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); ``` -- a new consumer group consume from oldest postion at first startup, then consume from last time's offset at next startup, that is consume the unexpired message; +- 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); diff --git a/docs/en/proxy/deploy_guide.md b/docs/en/proxy/deploy_guide.md new file mode 100644 index 00000000000..346964856ca --- /dev/null +++ b/docs/en/proxy/deploy_guide.md @@ -0,0 +1,36 @@ +# 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`. diff --git a/example/pom.xml b/example/pom.xml index 2de90626914..9b11cf6766f 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -19,7 +19,7 @@ rocketmq-all org.apache.rocketmq - 4.9.4-SNAPSHOT + 5.1.3 4.0.0 @@ -27,11 +27,23 @@ rocketmq-example rocketmq-example ${project.version} + + ${basedir}/.. + + ${project.groupId} rocketmq-client + + ${project.groupId} + rocketmq-tools + + + ${project.groupId} + rocketmq-remoting + ${project.groupId} rocketmq-srvutil @@ -44,10 +56,6 @@ ${project.groupId} rocketmq-acl - - ch.qos.logback - logback-classic - org.javassist javassist @@ -55,12 +63,18 @@ io.jaegertracing jaeger-core - 1.6.0 io.jaegertracing jaeger-client - 1.6.0 + + + io.jaegertracing + jaeger-thrift + + + commons-cli + commons-cli diff --git a/example/src/main/java/org/apache/rocketmq/example/batch/SimpleBatchProducer.java b/example/src/main/java/org/apache/rocketmq/example/batch/SimpleBatchProducer.java index cf566aa15bd..cf82c2a874d 100644 --- a/example/src/main/java/org/apache/rocketmq/example/batch/SimpleBatchProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/batch/SimpleBatchProducer.java @@ -17,25 +17,34 @@ 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("BatchProducerGroupName"); + 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 - String topic = "BatchTest"; List messages = new ArrayList<>(); - messages.add(new Message(topic, "Tag", "OrderID001", "Hello world 0".getBytes())); - messages.add(new Message(topic, "Tag", "OrderID002", "Hello world 1".getBytes())); - messages.add(new Message(topic, "Tag", "OrderID003", "Hello world 2".getBytes())); + 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))); - producer.send(messages); + SendResult sendResult = producer.send(messages); + System.out.printf("%s", sendResult); } } diff --git a/example/src/main/java/org/apache/rocketmq/example/batch/SplitBatchProducer.java b/example/src/main/java/org/apache/rocketmq/example/batch/SplitBatchProducer.java index f9495c41714..d33a5a5bbaf 100644 --- a/example/src/main/java/org/apache/rocketmq/example/batch/SplitBatchProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/batch/SplitBatchProducer.java @@ -17,39 +17,50 @@ 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("BatchProducerGroupName"); + 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 - String topic = "BatchTest"; - List messages = new ArrayList<>(100 * 1000); - for (int i = 0; i < 100 * 1000; i++) { - messages.add(new Message(topic, "Tag", "OrderID" + i, ("Hello world " + i).getBytes())); + 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(); - producer.send(listItem); + SendResult sendResult = producer.send(listItem); + System.out.printf("%s", sendResult); } } } class ListSplitter implements Iterator> { - private int sizeLimit = 1000 * 1000; + private static final int SIZE_LIMIT = 1000 * 1000; private final List messages; private int currIndex; @@ -73,8 +84,9 @@ public List next() { for (Map.Entry entry : properties.entrySet()) { tmpSize += entry.getKey().length() + entry.getValue().length(); } - tmpSize = tmpSize + 20; //for log overhead - if (tmpSize > sizeLimit) { + //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) { @@ -83,7 +95,7 @@ public List next() { } break; } - if (tmpSize + totalSize > sizeLimit) { + if (tmpSize + totalSize > SIZE_LIMIT) { break; } else { totalSize += tmpSize; diff --git a/example/src/main/java/org/apache/rocketmq/example/benchmark/BatchProducer.java b/example/src/main/java/org/apache/rocketmq/example/benchmark/BatchProducer.java index cf207cd4518..c4a6162a5f5 100644 --- a/example/src/main/java/org/apache/rocketmq/example/benchmark/BatchProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/benchmark/BatchProducer.java @@ -21,31 +21,33 @@ import java.util.LinkedList; import java.util.List; import java.util.Random; -import java.util.Timer; -import java.util.TimerTask; 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.cli.PosixParser; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.RandomStringUtils; -import org.apache.rocketmq.acl.common.AclClientRPCHook; -import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; -import org.apache.rocketmq.client.log.ClientLogger; 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.logging.InternalLogger; +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 { @@ -53,9 +55,10 @@ 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 PosixParser()); + CommandLine commandLine = ServerUtil.parseCmdLine("benchmarkBatchProducer", args, buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { System.exit(-1); } @@ -70,11 +73,12 @@ public static void main(String[] args) throws MQClientException { final int tagCount = getOptionValue(commandLine, 'l', 0); final boolean msgTraceEnable = getOptionValue(commandLine, 'm', false); final boolean aclEnable = getOptionValue(commandLine, 'a', false); - final String ak = getOptionValue(commandLine, 'c', "rocketmq2"); - final String sk = getOptionValue(commandLine, 'e', "12346789"); + 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", - topic, threadCount, messageSize, batchSize, keyEnable, propertySize, tagCount, msgTraceEnable, aclEnable); + 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++) { @@ -82,13 +86,33 @@ public static void main(String[] args) throws MQClientException { } msgBody = sb.toString().getBytes(StandardCharsets.UTF_8); - final StatsBenchmarkBatchProducer statsBenchmark = new StatsBenchmarkBatchProducer(); + final StatsBenchmarkBatchProducer statsBenchmark = new StatsBenchmarkBatchProducer(reportInterval); statsBenchmark.start(); - final DefaultMQProducer producer = initInstance(namesrv, msgTraceEnable, aclEnable, ak, sk); + 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.getDefaultMQProducerImpl().setCompressType(CompressionType.of(compressType)); + producer.getDefaultMQProducerImpl().setCompressLevel(compressLevel); + producer.setCompressMsgBodyOverHowmuch(compressOverHowMuch); + System.out.printf("compressType: %s compressLevel: %s%n", compressType, compressLevel); + } else { + producer.setCompressMsgBodyOverHowmuch(Integer.MAX_VALUE); + } + producer.start(); - final InternalLogger log = ClientLogger.getLog(); + final Logger logger = LoggerFactory.getLogger(BatchProducer.class); final ExecutorService sendThreadPool = Executors.newFixedThreadPool(threadCount); for (int i = 0; i < threadCount; i++) { sendThreadPool.execute(new Runnable() { @@ -130,7 +154,7 @@ public void run() { } catch (RemotingException e) { statsBenchmark.getSendRequestFailedCount().increment(); statsBenchmark.getSendMessageFailedCount().add(msgs.size()); - log.error("[BENCHMARK_PRODUCER] Send Exception", e); + logger.error("[BENCHMARK_PRODUCER] Send Exception", e); try { Thread.sleep(3000); @@ -145,15 +169,15 @@ public void run() { } statsBenchmark.getSendRequestFailedCount().increment(); statsBenchmark.getSendMessageFailedCount().add(msgs.size()); - log.error("[BENCHMARK_PRODUCER] Send Exception", e); + logger.error("[BENCHMARK_PRODUCER] Send Exception", e); } catch (MQClientException e) { statsBenchmark.getSendRequestFailedCount().increment(); statsBenchmark.getSendMessageFailedCount().add(msgs.size()); - log.error("[BENCHMARK_PRODUCER] Send Exception", e); + logger.error("[BENCHMARK_PRODUCER] Send Exception", e); } catch (MQBrokerException e) { statsBenchmark.getSendRequestFailedCount().increment(); statsBenchmark.getSendMessageFailedCount().add(msgs.size()); - log.error("[BENCHMARK_PRODUCER] Send Exception", e); + logger.error("[BENCHMARK_PRODUCER] Send Exception", e); try { Thread.sleep(3000); } catch (InterruptedException ignored) { @@ -198,11 +222,11 @@ public static Options buildCommandlineOptions(final Options options) { opt.setRequired(false); options.addOption(opt); - opt = new Option("c", "accessKey", true, "Acl Access Key, Default: rocketmq2"); + opt = new Option("ak", "accessKey", true, "Acl Access Key, Default: rocketmq2"); opt.setRequired(false); options.addOption(opt); - opt = new Option("e", "secretKey", true, "Acl Secret Key, Default: 123456789"); + opt = new Option("sk", "secretKey", true, "Acl Secret Key, Default: 123456789"); opt.setRequired(false); options.addOption(opt); @@ -213,6 +237,27 @@ public static Options buildCommandlineOptions(final Options options) { 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; } @@ -291,14 +336,11 @@ private static void setProperties(int propertySize, List msgs) { } } - private static DefaultMQProducer initInstance(String namesrv, boolean traceEnable, boolean aclEnable, String ak, - String sk) { - RPCHook rpcHook = aclEnable ? new AclClientRPCHook(new SessionCredentials(ak, sk)) : null; + 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); - producer.setCompressMsgBodyOverHowmuch(Integer.MAX_VALUE); return producer; } } @@ -317,18 +359,25 @@ class StatsBenchmarkBatchProducer { private final LongAdder sendMessageFailedCount = new LongAdder(); - private final Timer timer = new Timer("BenchmarkTimerThread", true); + 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(), + System.currentTimeMillis(), + this.sendRequestSuccessCount.longValue(), + this.sendRequestFailedCount.longValue(), + this.sendMessageSuccessCount.longValue(), + this.sendMessageFailedCount.longValue(), + this.sendMessageSuccessTimeTotal.longValue(), }; return snap; @@ -360,7 +409,7 @@ public LongAdder getSendMessageFailedCount() { public void start() { - timer.scheduleAtFixedRate(new TimerTask() { + executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { snapshotList.addLast(createSnapshot()); @@ -368,9 +417,9 @@ public void run() { snapshotList.removeFirst(); } } - }, 1000, 1000); + }, 1000, 1000, TimeUnit.MILLISECONDS); - timer.scheduleAtFixedRate(new TimerTask() { + executorService.scheduleAtFixedRate(new Runnable() { private void printStats() { if (snapshotList.size() >= 10) { Long[] begin = snapshotList.getFirst(); @@ -381,8 +430,8 @@ private void printStats() { 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", - System.currentTimeMillis(), sendTps, sendMps, getSendMessageMaxRT().longValue(), averageRT, averageMsgRT, end[2], end[4]); + 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]); } } @@ -394,10 +443,10 @@ public void run() { e.printStackTrace(); } } - }, 10000, 10000); + }, reportInterval, reportInterval, TimeUnit.MILLISECONDS); } public void shutdown() { - timer.cancel(); + executorService.shutdown(); } } \ No newline at end of file diff --git a/example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java b/example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java index d08795d6b03..57270fcd006 100644 --- a/example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java @@ -17,11 +17,20 @@ 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.cli.PosixParser; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.MessageSelector; @@ -31,26 +40,20 @@ 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; -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; - 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 PosixParser()); + CommandLine commandLine = ServerUtil.parseCmdLine("benchmarkConsumer", args, buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { System.exit(-1); } @@ -64,21 +67,23 @@ public static void main(String[] args) throws MQClientException, IOException { 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%n", - topic, threadCount, group, isSuffixEnable, filterType, expression, msgTraceEnable, aclEnable); + 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()); + new BasicThreadFactory.Builder().namingPattern("BenchmarkTimerThread-%d").daemon(true).build()); - final LinkedList snapshotList = new LinkedList(); + final LinkedList snapshotList = new LinkedList<>(); executorService.scheduleAtFixedRate(new TimerTask() { @Override @@ -107,9 +112,8 @@ private void printStats() { statsBenchmarkConsumer.getBorn2ConsumerMaxRT().set(0); statsBenchmarkConsumer.getStore2ConsumerMaxRT().set(0); - System.out.printf("Current Time: %s TPS: %d FAIL: %d AVG(B2C) RT(ms): %7.3f AVG(S2C) RT(ms): %7.3f MAX(B2C) RT(ms): %d MAX(S2C) RT(ms): %d%n", - System.currentTimeMillis(), consumeTps, failCount, averageB2CRT, averageS2CRT, b2cMax, s2cMax - ); + 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); } } @@ -121,7 +125,7 @@ public void run() { e.printStackTrace(); } } - }, 10000, 10000, TimeUnit.MILLISECONDS); + }, reportInterval, reportInterval, TimeUnit.MILLISECONDS); RPCHook rpcHook = null; if (aclEnable) { @@ -137,6 +141,7 @@ public void run() { consumer.setConsumeThreadMin(threadCount); consumer.setConsumeThreadMax(threadCount); consumer.setInstanceName(Long.toString(System.currentTimeMillis())); + consumer.setClientRebalance(clientRebalanceEnable); if (filterType == null || expression == null) { consumer.subscribe(topic, "*"); @@ -231,6 +236,10 @@ public static Options buildCommandlineOptions(final Options options) { 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; } diff --git a/example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java b/example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java index c32e00e97b8..480d16b7581 100644 --- a/example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java +++ b/example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java @@ -17,21 +17,28 @@ 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.cli.PosixParser; 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.log.ClientLogger; 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.logging.InternalLogger; 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; @@ -47,18 +54,22 @@ 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 PosixParser()); + 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 threadCount = commandLine.hasOption('w') ? Integer.parseInt(commandLine.getOptionValue('w')) : 64; 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; @@ -68,9 +79,16 @@ public static void main(String[] args) throws MQClientException { 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%n delayEnable: %s%n delayLevel: %s%n", - topic, threadCount, messageSize, keyEnable, propertySize, tagCount, msgTraceEnable, aclEnable, messageNum, delayEnable, delayLevel); + 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++) { @@ -78,8 +96,6 @@ public static void main(String[] args) throws MQClientException { } msgBody = sb.toString().getBytes(StandardCharsets.UTF_8); - final InternalLogger log = ClientLogger.getLog(); - final ExecutorService sendThreadPool = Executors.newFixedThreadPool(threadCount); final StatsBenchmarkProducer statsBenchmark = new StatsBenchmarkProducer(); @@ -87,7 +103,7 @@ public static void main(String[] args) throws MQClientException { ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("BenchmarkTimerThread-%d").daemon(true).build()); - final LinkedList snapshotList = new LinkedList(); + final LinkedList snapshotList = new LinkedList<>(); final long[] msgNums = new long[threadCount]; @@ -124,7 +140,7 @@ public void run() { e.printStackTrace(); } } - }, 10000, 10000, TimeUnit.MILLISECONDS); + }, reportInterval, reportInterval, TimeUnit.MILLISECONDS); RPCHook rpcHook = null; if (aclEnable) { @@ -140,7 +156,17 @@ public void run() { producer.setNamesrvAddr(ns); } - producer.setCompressMsgBodyOverHowmuch(Integer.MAX_VALUE); + 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.getDefaultMQProducerImpl().setCompressType(CompressionType.of(compressType)); + producer.getDefaultMQProducerImpl().setCompressLevel(compressLevel); + producer.setCompressMsgBodyOverHowmuch(compressOverHowMuch); + System.out.printf("compressType: %s compressLevel: %s%n", compressType, compressLevel); + } else { + producer.setCompressMsgBodyOverHowmuch(Integer.MAX_VALUE); + } producer.start(); @@ -186,18 +212,26 @@ public void run() { startValue += 2; } } - producer.send(msg); - 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(); + 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(); @@ -253,6 +287,21 @@ public void run() { } } + 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); @@ -302,6 +351,30 @@ public static Options buildCommandlineOptions(final Options options) { 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; } @@ -317,12 +390,12 @@ private static void doPrintStats(final LinkedList snapshotList, final St 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", + 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", - System.currentTimeMillis(), sendTps, statsBenchmark.getSendMessageMaxRT().longValue(), averageRT, end[2], end[4]); + 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]); } } } diff --git a/example/src/main/java/org/apache/rocketmq/example/benchmark/TransactionProducer.java b/example/src/main/java/org/apache/rocketmq/example/benchmark/TransactionProducer.java index 5e2f287e44b..34cdeb49dbb 100644 --- a/example/src/main/java/org/apache/rocketmq/example/benchmark/TransactionProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/benchmark/TransactionProducer.java @@ -18,9 +18,9 @@ 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.cli.PosixParser; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.LocalTransactionState; @@ -28,10 +28,13 @@ 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; @@ -61,8 +64,9 @@ public class TransactionProducer { 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 PosixParser()); + 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; @@ -75,13 +79,14 @@ public static void main(String[] args) throws MQClientException, UnsupportedEnco 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()); + new BasicThreadFactory.Builder().namingPattern("BenchmarkTimerThread-%d").daemon(true).build()); final LinkedList snapshotList = new LinkedList<>(); @@ -101,8 +106,7 @@ private void printStats() { Snapshot begin = snapshotList.getFirst(); Snapshot end = snapshotList.getLast(); - final long sendCount = (end.sendRequestSuccessCount - begin.sendRequestSuccessCount) - + (end.sendRequestFailedCount - begin.sendRequestFailedCount); + 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); @@ -112,9 +116,9 @@ private void printStats() { 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", - System.currentTimeMillis(), sendTps, statsBenchmark.getSendMessageMaxRT().get(), averageRT, failCount, checkCount, - unexpectedCheck, dupCheck); + "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); } } @@ -127,7 +131,7 @@ public void run() { e.printStackTrace(); } } - }, 10000, 10000, TimeUnit.MILLISECONDS); + }, config.reportInterval, config.reportInterval, TimeUnit.MILLISECONDS); RPCHook rpcHook = null; if (config.aclEnable) { @@ -137,11 +141,11 @@ public void run() { } final TransactionListener transactionCheckListener = new TransactionListenerImpl(statsBenchmark, config); final TransactionMQProducer producer = new TransactionMQProducer( - null, - "benchmark_transaction_producer", - rpcHook, - config.msgTraceEnable, - null); + null, + "benchmark_transaction_producer", + rpcHook, + config.msgTraceEnable, + null); producer.setInstanceName(Long.toString(System.currentTimeMillis())); producer.setTransactionListener(transactionCheckListener); producer.setDefaultTopicQueueNums(1000); @@ -160,7 +164,7 @@ public void run() { final long beginTimestamp = System.currentTimeMillis(); try { SendResult sendResult = - producer.sendMessageInTransaction(buildMessage(config), null); + producer.sendMessageInTransaction(buildMessage(config), null); success = sendResult != null && sendResult.getSendStatus() == SendStatus.SEND_OK; } catch (Throwable e) { success = false; @@ -170,7 +174,7 @@ public void run() { long prevMaxRT = statsBenchmark.getSendMessageMaxRT().get(); while (currentRT > prevMaxRT) { boolean updated = statsBenchmark.getSendMessageMaxRT() - .compareAndSet(prevMaxRT, currentRT); + .compareAndSet(prevMaxRT, currentRT); if (updated) break; @@ -287,6 +291,10 @@ public static Options buildCommandlineOptions(final Options options) { 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; } } @@ -361,10 +369,10 @@ public LocalTransactionState checkLocalTransaction(MessageExt msg) { } 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()); + 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; } @@ -373,9 +381,9 @@ public LocalTransactionState checkLocalTransaction(MessageExt msg) { 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); + 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; } @@ -471,6 +479,7 @@ class TxSendConfig { int sendInterval; boolean aclEnable; boolean msgTraceEnable; + int reportInterval; } class LRUMap extends LinkedHashMap { diff --git a/example/src/main/java/org/apache/rocketmq/example/benchmark/timer/TimerConsumer.java b/example/src/main/java/org/apache/rocketmq/example/benchmark/timer/TimerConsumer.java new file mode 100644 index 00000000000..f2ab6b57310 --- /dev/null +++ b/example/src/main/java/org/apache/rocketmq/example/benchmark/timer/TimerConsumer.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } + } + +} diff --git a/example/src/main/java/org/apache/rocketmq/example/benchmark/timer/TimerProducer.java b/example/src/main/java/org/apache/rocketmq/example/benchmark/timer/TimerProducer.java new file mode 100644 index 00000000000..3e92ff1b0be --- /dev/null +++ b/example/src/main/java/org/apache/rocketmq/example/benchmark/timer/TimerProducer.java @@ -0,0 +1,322 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } + } + +} diff --git a/example/src/main/java/org/apache/rocketmq/example/broadcast/PushConsumer.java b/example/src/main/java/org/apache/rocketmq/example/broadcast/PushConsumer.java index 28e02341c69..e991dfeab2c 100644 --- a/example/src/main/java/org/apache/rocketmq/example/broadcast/PushConsumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/broadcast/PushConsumer.java @@ -16,38 +16,40 @@ */ package org.apache.rocketmq.example.broadcast; -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; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; +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("please_rename_unique_group_name_1"); + + 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("TopicTest", "TagA || TagC || TagD"); - - consumer.registerMessageListener(new MessageListenerConcurrently() { + consumer.subscribe(TOPIC, SUB_EXPRESSION); - @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.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"); } -} \ No newline at end of file +} diff --git a/example/src/main/java/org/apache/rocketmq/example/filter/TagFilterConsumer.java b/example/src/main/java/org/apache/rocketmq/example/filter/TagFilterConsumer.java index ba3723cdfef..74fc4a9ee43 100644 --- a/example/src/main/java/org/apache/rocketmq/example/filter/TagFilterConsumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/filter/TagFilterConsumer.java @@ -16,7 +16,6 @@ */ package org.apache.rocketmq.example.filter; -import java.io.IOException; import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; @@ -27,7 +26,7 @@ public class TagFilterConsumer { - public static void main(String[] args) throws InterruptedException, MQClientException, IOException { + public static void main(String[] args) throws MQClientException { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name"); diff --git a/example/src/main/java/org/apache/rocketmq/example/namespace/ProducerWithNamespace.java b/example/src/main/java/org/apache/rocketmq/example/namespace/ProducerWithNamespace.java index ed47c96face..a8a920b1d71 100644 --- a/example/src/main/java/org/apache/rocketmq/example/namespace/ProducerWithNamespace.java +++ b/example/src/main/java/org/apache/rocketmq/example/namespace/ProducerWithNamespace.java @@ -20,14 +20,25 @@ 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("InstanceTest", "pidTest"); - producer.setNamesrvAddr("127.0.0.1:9876"); + DefaultMQProducer producer = new DefaultMQProducer(NAMESPACE, PRODUCER_GROUP); + + producer.setNamesrvAddr(DEFAULT_NAMESRVADDR); producer.start(); - for (int i = 0; i < 100; i++) { - Message message = new Message("topicTest", "tagTest", "Hello world".getBytes()); + 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()); @@ -35,5 +46,6 @@ public static void main(String[] args) throws Exception { e.printStackTrace(); } } + producer.shutdown(); } } \ No newline at end of file diff --git a/example/src/main/java/org/apache/rocketmq/example/namespace/PullConsumerWithNamespace.java b/example/src/main/java/org/apache/rocketmq/example/namespace/PullConsumerWithNamespace.java index f8bf0230d56..9ca1b35b95b 100644 --- a/example/src/main/java/org/apache/rocketmq/example/namespace/PullConsumerWithNamespace.java +++ b/example/src/main/java/org/apache/rocketmq/example/namespace/PullConsumerWithNamespace.java @@ -25,14 +25,20 @@ import org.apache.rocketmq.common.message.MessageQueue; public class PullConsumerWithNamespace { - private static final Map OFFSE_TABLE = new HashMap(); + + 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("InstanceTest", "cidTest"); - pullConsumer.setNamesrvAddr("127.0.0.1:9876"); + DefaultMQPullConsumer pullConsumer = new DefaultMQPullConsumer(NAMESPACE, CONSUMER_GROUP); + pullConsumer.setNamesrvAddr(DEFAULT_NAMESRVADDR); pullConsumer.start(); - Set mqs = pullConsumer.fetchSubscribeMessageQueues("topicTest"); + 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: @@ -66,7 +72,7 @@ public static void main(String[] args) throws Exception { } private static long getMessageQueueOffset(MessageQueue mq) { - Long offset = OFFSE_TABLE.get(mq); + Long offset = OFFSET_TABLE.get(mq); if (offset != null) { return offset; } @@ -78,11 +84,11 @@ private static void dealWithPullResult(PullResult pullResult) { if (null == pullResult || pullResult.getMsgFoundList().isEmpty()) { return; } - pullResult.getMsgFoundList().stream().forEach( - (msg) -> System.out.printf("Topic is:%s, msgId is:%s%n" , msg.getTopic(), msg.getMsgId())); + 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) { - OFFSE_TABLE.put(mq, offset); + OFFSET_TABLE.put(mq, offset); } } \ No newline at end of file diff --git a/example/src/main/java/org/apache/rocketmq/example/namespace/PushConsumerWithNamespace.java b/example/src/main/java/org/apache/rocketmq/example/namespace/PushConsumerWithNamespace.java index a0fdeeffbba..181720ea217 100644 --- a/example/src/main/java/org/apache/rocketmq/example/namespace/PushConsumerWithNamespace.java +++ b/example/src/main/java/org/apache/rocketmq/example/namespace/PushConsumerWithNamespace.java @@ -21,15 +21,18 @@ 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("InstanceTest", "cidTest"); - defaultMQPushConsumer.setNamesrvAddr("127.0.0.1:9876"); - defaultMQPushConsumer.subscribe("topicTest", "*"); - defaultMQPushConsumer.registerMessageListener((MessageListenerConcurrently)(msgs, context) -> { - msgs.stream().forEach((msg) -> { - System.out.printf("Msg topic is:%s, MsgId is:%s, reconsumeTimes is:%s%n", msg.getTopic() , msg.getMsgId(), msg.getReconsumeTimes()); - }); - return ConsumeConcurrentlyStatus.RECONSUME_LATER; + DefaultMQPushConsumer defaultMQPushConsumer = new DefaultMQPushConsumer(NAMESPACE, CONSUMER_GROUP); + 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(); diff --git a/example/src/main/java/org/apache/rocketmq/example/openmessaging/SimpleProducer.java b/example/src/main/java/org/apache/rocketmq/example/openmessaging/SimpleProducer.java index dbe1d105688..ec72aa0dd77 100644 --- a/example/src/main/java/org/apache/rocketmq/example/openmessaging/SimpleProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/openmessaging/SimpleProducer.java @@ -17,19 +17,23 @@ package org.apache.rocketmq.example.openmessaging; 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 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("oms:rocketmq://localhost:9876/default:default"); + OMS.getMessagingAccessPoint(URL); final Producer producer = messagingAccessPoint.createProducer(); @@ -40,7 +44,8 @@ public static void main(String[] args) { System.out.printf("Producer startup OK%n"); { - Message message = producer.createBytesMessage("OMS_HELLO_TOPIC", "OMS_HELLO_BODY".getBytes(Charset.forName("UTF-8"))); + 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()); @@ -48,28 +53,27 @@ public static void main(String[] args) { final CountDownLatch countDownLatch = new CountDownLatch(1); { - final Future result = producer.sendAsync(producer.createBytesMessage("OMS_HELLO_TOPIC", "OMS_HELLO_BODY".getBytes(Charset.forName("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(); + 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(Charset.forName("UTF-8")))); + 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); // Wait some time for one-way delivery. + // Wait some time for one-way delivery. + Thread.sleep(500); } catch (InterruptedException ignore) { } diff --git a/example/src/main/java/org/apache/rocketmq/example/openmessaging/SimplePullConsumer.java b/example/src/main/java/org/apache/rocketmq/example/openmessaging/SimplePullConsumer.java index 86aba410aee..9ad69b31b33 100644 --- a/example/src/main/java/org/apache/rocketmq/example/openmessaging/SimplePullConsumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/openmessaging/SimplePullConsumer.java @@ -23,18 +23,25 @@ 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("oms:rocketmq://localhost:9876/default:default"); + OMS.getMessagingAccessPoint(URL); messagingAccessPoint.startup(); final Producer producer = messagingAccessPoint.createProducer(); final PullConsumer consumer = messagingAccessPoint.createPullConsumer( - OMS.newKeyValue().put(OMSBuiltinKeys.CONSUMER_ID, "OMS_CONSUMER")); + OMS.newKeyValue().put(OMSBuiltinKeys.CONSUMER_ID, QUEUE)); messagingAccessPoint.startup(); System.out.printf("MessagingAccessPoint startup OK%n"); @@ -42,7 +49,7 @@ public static void main(String[] args) { final String queueName = "TopicTest"; producer.startup(); - Message msg = producer.createBytesMessage(queueName, "Hello Open Messaging".getBytes()); + 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(); diff --git a/example/src/main/java/org/apache/rocketmq/example/openmessaging/SimplePushConsumer.java b/example/src/main/java/org/apache/rocketmq/example/openmessaging/SimplePushConsumer.java index 220c1323061..92ccff1ba4d 100644 --- a/example/src/main/java/org/apache/rocketmq/example/openmessaging/SimplePushConsumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/openmessaging/SimplePushConsumer.java @@ -20,13 +20,18 @@ 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 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("oms:rocketmq://localhost:9876/default:default"); + .getMessagingAccessPoint(URL); final PushConsumer consumer = messagingAccessPoint. createPushConsumer(OMS.newKeyValue().put(OMSBuiltinKeys.CONSUMER_ID, "OMS_CONSUMER")); @@ -34,20 +39,14 @@ public static void main(String[] args) { messagingAccessPoint.startup(); System.out.printf("MessagingAccessPoint startup OK%n"); - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - @Override - public void run() { - consumer.shutdown(); - messagingAccessPoint.shutdown(); - } + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + 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.attachQueue(QUEUE, (message, context) -> { + System.out.printf("Received one message: %s%n", message.sysHeaders().getString(Message.BuiltinKeys.MESSAGE_ID)); + context.ack(); }); consumer.startup(); diff --git a/example/src/main/java/org/apache/rocketmq/example/operation/Consumer.java b/example/src/main/java/org/apache/rocketmq/example/operation/Consumer.java index 6936f1dcea3..90f2e133a1c 100644 --- a/example/src/main/java/org/apache/rocketmq/example/operation/Consumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/operation/Consumer.java @@ -19,11 +19,11 @@ 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.commons.cli.PosixParser; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; @@ -33,7 +33,7 @@ public class Consumer { - public static void main(String[] args) throws InterruptedException, MQClientException { + public static void main(String[] args) throws MQClientException { CommandLine commandLine = buildCommandline(args); if (commandLine != null) { String group = commandLine.getOptionValue('g'); @@ -91,7 +91,7 @@ public static CommandLine buildCommandline(String[] args) { opt.setRequired(true); options.addOption(opt); - PosixParser parser = new PosixParser(); + DefaultParser parser = new DefaultParser(); HelpFormatter hf = new HelpFormatter(); hf.setWidth(110); CommandLine commandLine = null; diff --git a/example/src/main/java/org/apache/rocketmq/example/operation/Producer.java b/example/src/main/java/org/apache/rocketmq/example/operation/Producer.java index 1d4336d7fa9..0cf260ddb74 100644 --- a/example/src/main/java/org/apache/rocketmq/example/operation/Producer.java +++ b/example/src/main/java/org/apache/rocketmq/example/operation/Producer.java @@ -17,11 +17,11 @@ 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.commons.cli.PosixParser; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; @@ -89,7 +89,7 @@ public static CommandLine buildCommandline(String[] args) { opt.setRequired(true); options.addOption(opt); - PosixParser parser = new PosixParser(); + DefaultParser parser = new DefaultParser(); HelpFormatter hf = new HelpFormatter(); hf.setWidth(110); CommandLine commandLine = null; diff --git a/example/src/main/java/org/apache/rocketmq/example/ordermessage/Producer.java b/example/src/main/java/org/apache/rocketmq/example/ordermessage/Producer.java index c5d864fa3f6..8ee11bf2b47 100644 --- a/example/src/main/java/org/apache/rocketmq/example/ordermessage/Producer.java +++ b/example/src/main/java/org/apache/rocketmq/example/ordermessage/Producer.java @@ -16,7 +16,6 @@ */ package org.apache.rocketmq.example.ordermessage; -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.MessageQueueSelector; @@ -24,13 +23,11 @@ import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.common.RemotingHelper; -import org.apache.rocketmq.remoting.exception.RemotingException; -import java.io.UnsupportedEncodingException; import java.util.List; public class Producer { - public static void main(String[] args) throws UnsupportedEncodingException { + public static void main(String[] args) throws MQClientException { try { DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); producer.start(); @@ -54,8 +51,9 @@ public MessageQueue select(List mqs, Message msg, Object arg) { } producer.shutdown(); - } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) { + } catch (Exception e) { e.printStackTrace(); + throw new MQClientException(e.getMessage(), null); } } } diff --git a/example/src/main/java/org/apache/rocketmq/example/quickstart/Consumer.java b/example/src/main/java/org/apache/rocketmq/example/quickstart/Consumer.java index 07e572402d2..b104016fb56 100644 --- a/example/src/main/java/org/apache/rocketmq/example/quickstart/Consumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/quickstart/Consumer.java @@ -16,26 +16,27 @@ */ package org.apache.rocketmq.example.quickstart; -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; /** * This example shows how to subscribe and consume messages using providing {@link DefaultMQPushConsumer}. */ public class Consumer { - public static void main(String[] args) throws InterruptedException, MQClientException { + 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("please_rename_unique_group_name_4"); + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); /* * Specify name server addresses. @@ -48,6 +49,8 @@ public static void main(String[] args) throws InterruptedException, MQClientExce * } * */ + // 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. @@ -57,19 +60,14 @@ public static void main(String[] args) throws InterruptedException, MQClientExce /* * Subscribe one more topic to consume. */ - consumer.subscribe("TopicTest", "*"); + consumer.subscribe(TOPIC, "*"); /* * 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); - return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; - } + consumer.registerMessageListener((MessageListenerConcurrently) (msg, context) -> { + System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msg); + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); /* diff --git a/example/src/main/java/org/apache/rocketmq/example/quickstart/Producer.java b/example/src/main/java/org/apache/rocketmq/example/quickstart/Producer.java index 771eea1598e..2c67e463e6f 100644 --- a/example/src/main/java/org/apache/rocketmq/example/quickstart/Producer.java +++ b/example/src/main/java/org/apache/rocketmq/example/quickstart/Producer.java @@ -26,38 +26,49 @@ * 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("please_rename_unique_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");
    +         *  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 < 1000; i++) { + for (int i = 0; i < MESSAGE_COUNT; i++) { try { /* * Create a message instance, specifying topic, tag and message body. */ - Message msg = new Message("TopicTest" /* Topic */, - "TagA" /* Tag */, + Message msg = new Message(TOPIC /* Topic */, + TAG /* Tag */, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); @@ -107,7 +118,7 @@ public static void main(String[] args) throws MQClientException, InterruptedExce } /* - * Shut down once the producer instance is not longer in use. + * Shut down once the producer instance is no longer in use. */ producer.shutdown(); } diff --git a/example/src/main/java/org/apache/rocketmq/example/rpc/AsyncRequestProducer.java b/example/src/main/java/org/apache/rocketmq/example/rpc/AsyncRequestProducer.java index 072291d5c2e..31df559b15d 100644 --- a/example/src/main/java/org/apache/rocketmq/example/rpc/AsyncRequestProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/rpc/AsyncRequestProducer.java @@ -18,15 +18,15 @@ package org.apache.rocketmq.example.rpc; import org.apache.rocketmq.client.exception.MQClientException; -import org.apache.rocketmq.client.log.ClientLogger; 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.logging.InternalLogger; 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 InternalLogger log = ClientLogger.getLog(); + 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"; diff --git a/example/src/main/java/org/apache/rocketmq/example/rpc/RequestProducer.java b/example/src/main/java/org/apache/rocketmq/example/rpc/RequestProducer.java index b34908b84f3..69048de2d0e 100644 --- a/example/src/main/java/org/apache/rocketmq/example/rpc/RequestProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/rpc/RequestProducer.java @@ -29,6 +29,10 @@ public static void main(String[] args) throws MQClientException, InterruptedExce 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 { diff --git a/example/src/main/java/org/apache/rocketmq/example/rpc/ResponseConsumer.java b/example/src/main/java/org/apache/rocketmq/example/rpc/ResponseConsumer.java index c62c7d4eb6d..a1c18ae698d 100644 --- a/example/src/main/java/org/apache/rocketmq/example/rpc/ResponseConsumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/rpc/ResponseConsumer.java @@ -17,6 +17,7 @@ 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; @@ -40,11 +41,13 @@ public static void main(String[] args) throws InterruptedException, MQClientExce // 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); @@ -55,9 +58,9 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeCo 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()); + System.out.printf("handle message: %s %n", msg.toString()); String replyTo = MessageUtil.getReplyToClient(msg); - byte[] replyContent = "reply message contents.".getBytes(); + 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); diff --git a/example/src/main/java/org/apache/rocketmq/example/schedule/ScheduledMessageConsumer.java b/example/src/main/java/org/apache/rocketmq/example/schedule/ScheduledMessageConsumer.java index fdb4c86eb1f..b3fab65f0ab 100644 --- a/example/src/main/java/org/apache/rocketmq/example/schedule/ScheduledMessageConsumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/schedule/ScheduledMessageConsumer.java @@ -17,31 +17,33 @@ package org.apache.rocketmq.example.schedule; 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 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("ExampleConsumer"); + 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("TestTopic", "*"); + consumer.subscribe(TOPIC, "*"); // 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.printf("Receive message[msgId=%s %d ms later]\n", message.getMsgId(), - System.currentTimeMillis() - message.getStoreTimestamp()); - } - return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + 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(); diff --git a/example/src/main/java/org/apache/rocketmq/example/schedule/ScheduledMessageProducer.java b/example/src/main/java/org/apache/rocketmq/example/schedule/ScheduledMessageProducer.java index 0f6b722d530..aeae492dd9e 100644 --- a/example/src/main/java/org/apache/rocketmq/example/schedule/ScheduledMessageProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/schedule/ScheduledMessageProducer.java @@ -16,26 +16,38 @@ */ 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("ExampleProducerGroup"); + 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("TestTopic", ("Hello scheduled message " + i).getBytes()); + 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 - producer.send(message); + SendResult result = producer.send(message); + System.out.print(result); } - + // Shutdown producer after use. producer.shutdown(); } - + } \ No newline at end of file diff --git a/example/src/main/java/org/apache/rocketmq/example/schedule/TimerMessageConsumer.java b/example/src/main/java/org/apache/rocketmq/example/schedule/TimerMessageConsumer.java new file mode 100644 index 00000000000..788983592f9 --- /dev/null +++ b/example/src/main/java/org/apache/rocketmq/example/schedule/TimerMessageConsumer.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + } +} diff --git a/example/src/main/java/org/apache/rocketmq/example/schedule/TimerMessageProducer.java b/example/src/main/java/org/apache/rocketmq/example/schedule/TimerMessageProducer.java new file mode 100644 index 00000000000..c4e3b4f3a19 --- /dev/null +++ b/example/src/main/java/org/apache/rocketmq/example/schedule/TimerMessageProducer.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/AclClient.java b/example/src/main/java/org/apache/rocketmq/example/simple/AclClient.java index 0c97cd33210..15134a52109 100644 --- a/example/src/main/java/org/apache/rocketmq/example/simple/AclClient.java +++ b/example/src/main/java/org/apache/rocketmq/example/simple/AclClient.java @@ -16,6 +16,7 @@ */ package org.apache.rocketmq.example.simple; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,7 +44,7 @@ public class AclClient { - private static final Map OFFSE_TABLE = new HashMap(); + private static final Map OFFSE_TABLE = new HashMap<>(); private static final String ACL_ACCESS_KEY = "RocketMQ"; @@ -145,7 +146,7 @@ private static void printBody(List msg) { return; for (MessageExt m : msg) { if (m != null) { - System.out.printf("msgId : %s body : %s \n\r", m.getMsgId(), new String(m.getBody())); + System.out.printf("msgId : %s body : %s \n\r", m.getMsgId(), new String(m.getBody(), StandardCharsets.UTF_8)); } } } diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/AsyncProducer.java b/example/src/main/java/org/apache/rocketmq/example/simple/AsyncProducer.java index d40739c813d..42d19b1b6fd 100644 --- a/example/src/main/java/org/apache/rocketmq/example/simple/AsyncProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/simple/AsyncProducer.java @@ -32,6 +32,8 @@ public static void main( 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; diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/CachedQueue.java b/example/src/main/java/org/apache/rocketmq/example/simple/CachedQueue.java index 9e6ab92c7d5..41e28c59152 100644 --- a/example/src/main/java/org/apache/rocketmq/example/simple/CachedQueue.java +++ b/example/src/main/java/org/apache/rocketmq/example/simple/CachedQueue.java @@ -21,7 +21,7 @@ import org.apache.rocketmq.common.message.MessageExt; public class CachedQueue { - private final TreeMap msgCachedTable = new TreeMap(); + private final TreeMap msgCachedTable = new TreeMap<>(); public TreeMap getMsgCachedTable() { return msgCachedTable; diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/LitePullConsumerAssign.java b/example/src/main/java/org/apache/rocketmq/example/simple/LitePullConsumerAssign.java index e638de1c960..0d8fc1c6985 100644 --- a/example/src/main/java/org/apache/rocketmq/example/simple/LitePullConsumerAssign.java +++ b/example/src/main/java/org/apache/rocketmq/example/simple/LitePullConsumerAssign.java @@ -43,7 +43,7 @@ public static void main(String[] args) throws Exception { while (running) { List messageExts = litePullConsumer.poll(); System.out.printf("%s %n", messageExts); - litePullConsumer.commitSync(); + litePullConsumer.commit(); } } finally { litePullConsumer.shutdown(); diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/LitePullConsumerAssignWithSubExpression.java b/example/src/main/java/org/apache/rocketmq/example/simple/LitePullConsumerAssignWithSubExpression.java new file mode 100644 index 00000000000..fb673df3f82 --- /dev/null +++ b/example/src/main/java/org/apache/rocketmq/example/simple/LitePullConsumerAssignWithSubExpression.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } + + } +} diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/OnewayProducer.java b/example/src/main/java/org/apache/rocketmq/example/simple/OnewayProducer.java index 6932f669fc1..e4bb06678ea 100644 --- a/example/src/main/java/org/apache/rocketmq/example/simple/OnewayProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/simple/OnewayProducer.java @@ -18,7 +18,8 @@ import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; -import org.apache.rocketmq.remoting.common.RemotingHelper; + +import java.nio.charset.StandardCharsets; public class OnewayProducer { public static void main(String[] args) throws Exception { @@ -33,7 +34,7 @@ public static void main(String[] args) throws Exception { Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + - i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ + i).getBytes(StandardCharsets.UTF_8) /* Message body */ ); //Call send message to deliver message to one of brokers. producer.sendOneway(msg); diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/PopConsumer.java b/example/src/main/java/org/apache/rocketmq/example/simple/PopConsumer.java new file mode 100644 index 00000000000..6321e36d9ac --- /dev/null +++ b/example/src/main/java/org/apache/rocketmq/example/simple/PopConsumer.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; + } + }); + consumer.setClientRebalance(false); + consumer.start(); + System.out.printf("Consumer Started.%n"); + } + private static void switchPop() throws Exception { + DefaultMQAdminExt mqAdminExt = new DefaultMQAdminExt(); + 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); + } + } + } +} diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/Producer.java b/example/src/main/java/org/apache/rocketmq/example/simple/Producer.java index 448f8ee9f45..920d481b939 100644 --- a/example/src/main/java/org/apache/rocketmq/example/simple/Producer.java +++ b/example/src/main/java/org/apache/rocketmq/example/simple/Producer.java @@ -20,28 +20,32 @@ 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; +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("ProducerGroupName"); - producer.start(); + DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP); - 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); - } + // 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(); } diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/PullConsumer.java b/example/src/main/java/org/apache/rocketmq/example/simple/PullConsumer.java index ff9ef9ca34e..5ac8d247d95 100644 --- a/example/src/main/java/org/apache/rocketmq/example/simple/PullConsumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/simple/PullConsumer.java @@ -21,13 +21,13 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; 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; @@ -40,17 +40,12 @@ 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 better to register topics,It will use in rebalance when starting + //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 ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return new Thread(r, "PullConsumerThread"); - } - }); + ExecutorService executors = Executors.newFixedThreadPool(topics.size(), new ThreadFactoryImpl("PullConsumerThread")); for (String topic : consumer.getRegisterTopics()) { executors.execute(new Runnable() { @@ -137,7 +132,7 @@ public long consumeFromOffset(MessageQueue messageQueue) throws MQClientExceptio public void incPullTPS(String topic, int pullSize) { consumer.getDefaultMQPullConsumerImpl().getRebalanceImpl().getmQClientFactory() - .getConsumerStatsManager().incPullTPS(consumer.getConsumerGroup(), topic, pullSize); + .getConsumerStatsManager().incPullTPS(consumer.getConsumerGroup(), topic, pullSize); } }); diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/PullConsumerTest.java b/example/src/main/java/org/apache/rocketmq/example/simple/PullConsumerTest.java deleted file mode 100644 index f12595a903b..00000000000 --- a/example/src/main/java/org/apache/rocketmq/example/simple/PullConsumerTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF 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.DefaultMQPullConsumer; -import org.apache.rocketmq.client.consumer.PullResult; -import org.apache.rocketmq.client.exception.MQClientException; -import org.apache.rocketmq.common.message.MessageQueue; - -public class PullConsumerTest { - 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"); - consumer.start(); - - try { - MessageQueue mq = new MessageQueue(); - mq.setQueueId(0); - mq.setTopic("TopicTest3"); - mq.setBrokerName("vivedeMacBook-Pro.local"); - - long offset = 26; - - long beginTime = System.currentTimeMillis(); - PullResult pullResult = consumer.pullBlockIfNotFound(mq, null, offset, 32); - System.out.printf("%s%n", System.currentTimeMillis() - beginTime); - System.out.printf("%s%n", pullResult); - } catch (Exception e) { - e.printStackTrace(); - } - - consumer.shutdown(); - } -} diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/PullScheduleService.java b/example/src/main/java/org/apache/rocketmq/example/simple/PullScheduleService.java index 8cfdd9bbd0c..c652b065e4d 100644 --- a/example/src/main/java/org/apache/rocketmq/example/simple/PullScheduleService.java +++ b/example/src/main/java/org/apache/rocketmq/example/simple/PullScheduleService.java @@ -24,7 +24,7 @@ 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.common.protocol.heartbeat.MessageModel; +import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; public class PullScheduleService { diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/PushConsumer.java b/example/src/main/java/org/apache/rocketmq/example/simple/PushConsumer.java index abbfbdffcdd..9de2b01d49b 100644 --- a/example/src/main/java/org/apache/rocketmq/example/simple/PushConsumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/simple/PushConsumer.java @@ -26,10 +26,17 @@ 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("CID_JODIE_1"); - consumer.subscribe("TopicTest", "*"); + + 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"); diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/RandomAsyncCommit.java b/example/src/main/java/org/apache/rocketmq/example/simple/RandomAsyncCommit.java index 031cb151360..d4bd0ea6e4f 100644 --- a/example/src/main/java/org/apache/rocketmq/example/simple/RandomAsyncCommit.java +++ b/example/src/main/java/org/apache/rocketmq/example/simple/RandomAsyncCommit.java @@ -24,7 +24,7 @@ public class RandomAsyncCommit { private final ConcurrentHashMap mqCachedTable = - new ConcurrentHashMap(); + new ConcurrentHashMap<>(); public void putMessages(final MessageQueue mq, final List msgs) { CachedQueue cachedQueue = this.mqCachedTable.get(mq); diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/TestProducer.java b/example/src/main/java/org/apache/rocketmq/example/simple/TestProducer.java deleted file mode 100644 index 576f9cb6fbf..00000000000 --- a/example/src/main/java/org/apache/rocketmq/example/simple/TestProducer.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF 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.QueryResult; -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.common.message.MessageExt; -import org.apache.rocketmq.remoting.common.RemotingHelper; - -public class TestProducer { - public static void main(String[] args) throws MQClientException, InterruptedException { - DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName"); - producer.start(); - - for (int i = 0; i < 1; i++) - try { - { - Message msg = new Message("TopicTest1", - "TagA", - "key113", - "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); - SendResult sendResult = producer.send(msg); - System.out.printf("%s%n", sendResult); - - QueryResult queryMessage = - producer.queryMessage("TopicTest1", "key113", 10, 0, System.currentTimeMillis()); - for (MessageExt m : queryMessage.getMessageList()) { - System.out.printf("%s%n", m); - } - } - - } catch (Exception e) { - e.printStackTrace(); - } - producer.shutdown(); - } -} diff --git a/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingProducer.java b/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingProducer.java index cd9ae2792b6..0c41b5b6f18 100644 --- a/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingProducer.java @@ -29,19 +29,29 @@ 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("ProducerGroupName"); + 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("TopicTest", - "TagA", - "OrderID188", - "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); + Message msg = new Message(TOPIC, + TAG, + KEY, + "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); @@ -54,14 +64,14 @@ public static void main(String[] args) throws MQClientException { private static Tracer initTracer() { Configuration.SamplerConfiguration samplerConfig = Configuration.SamplerConfiguration.fromEnv() - .withType(ConstSampler.TYPE) - .withParam(1); + .withType(ConstSampler.TYPE) + .withParam(1); Configuration.ReporterConfiguration reporterConfig = Configuration.ReporterConfiguration.fromEnv() - .withLogSpans(true); + .withLogSpans(true); Configuration config = new Configuration("rocketmq") - .withSampler(samplerConfig) - .withReporter(reporterConfig); + .withSampler(samplerConfig) + .withReporter(reporterConfig); GlobalTracer.registerIfAbsent(config.getTracer()); return config.getTracer(); } diff --git a/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingPushConsumer.java b/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingPushConsumer.java index 1d5d8a273b2..9ac7c1634c7 100644 --- a/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingPushConsumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingPushConsumer.java @@ -22,34 +22,34 @@ import io.opentracing.Tracer; import io.opentracing.util.GlobalTracer; 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.client.trace.hook.ConsumeMessageOpenTracingHookImpl; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; -import org.apache.rocketmq.common.message.MessageExt; - -import java.util.List; 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("CID_JODIE_1"); - consumer.getDefaultMQPushConsumerImpl().registerConsumeMessageHook(new ConsumeMessageOpenTracingHookImpl(tracer)); + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); - consumer.subscribe("TopicTest", "*"); + // 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(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.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"); @@ -57,14 +57,14 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeCo private static Tracer initTracer() { Configuration.SamplerConfiguration samplerConfig = Configuration.SamplerConfiguration.fromEnv() - .withType(ConstSampler.TYPE) - .withParam(1); + .withType(ConstSampler.TYPE) + .withParam(1); Configuration.ReporterConfiguration reporterConfig = Configuration.ReporterConfiguration.fromEnv() - .withLogSpans(true); + .withLogSpans(true); Configuration config = new Configuration("rocketmq") - .withSampler(samplerConfig) - .withReporter(reporterConfig); + .withSampler(samplerConfig) + .withReporter(reporterConfig); GlobalTracer.registerIfAbsent(config.getTracer()); return config.getTracer(); } diff --git a/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingTransactionProducer.java b/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingTransactionProducer.java index 514f3ceb7fe..dc05c72b1c9 100644 --- a/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingTransactionProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingTransactionProducer.java @@ -35,10 +35,21 @@ 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("please_rename_unique_group_name"); + 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)); @@ -56,15 +67,15 @@ public LocalTransactionState checkLocalTransaction(MessageExt msg) { producer.start(); try { - Message msg = new Message("TopicTest", "Tag", "KEY", - "Hello RocketMQ".getBytes(RemotingHelper.DEFAULT_CHARSET)); + 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 < 100000; i++) { + for (int i = 0; i < MESSAGE_COUNT; i++) { Thread.sleep(1000); } producer.shutdown(); @@ -72,14 +83,14 @@ public LocalTransactionState checkLocalTransaction(MessageExt msg) { private static Tracer initTracer() { Configuration.SamplerConfiguration samplerConfig = Configuration.SamplerConfiguration.fromEnv() - .withType(ConstSampler.TYPE) - .withParam(1); + .withType(ConstSampler.TYPE) + .withParam(1); Configuration.ReporterConfiguration reporterConfig = Configuration.ReporterConfiguration.fromEnv() - .withLogSpans(true); + .withLogSpans(true); Configuration config = new Configuration("rocketmq") - .withSampler(samplerConfig) - .withReporter(reporterConfig); + .withSampler(samplerConfig) + .withReporter(reporterConfig); GlobalTracer.registerIfAbsent(config.getTracer()); return config.getTracer(); } diff --git a/example/src/main/java/org/apache/rocketmq/example/tracemessage/TraceProducer.java b/example/src/main/java/org/apache/rocketmq/example/tracemessage/TraceProducer.java index fb8e37fd2b7..add6c432334 100644 --- a/example/src/main/java/org/apache/rocketmq/example/tracemessage/TraceProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/tracemessage/TraceProducer.java @@ -24,17 +24,28 @@ 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("ProducerGroupName",true); + DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP, true); + + // 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++) + for (int i = 0; i < MESSAGE_COUNT; i++) { try { { - Message msg = new Message("TopicTest", - "TagA", - "OrderID188", + Message msg = new Message(TOPIC, + TAG, + KEY, "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); @@ -43,6 +54,7 @@ public static void main(String[] args) throws MQClientException, InterruptedExce } catch (Exception e) { e.printStackTrace(); } + } producer.shutdown(); } diff --git a/example/src/main/java/org/apache/rocketmq/example/tracemessage/TracePushConsumer.java b/example/src/main/java/org/apache/rocketmq/example/tracemessage/TracePushConsumer.java index 473351963f5..a833ee1e454 100644 --- a/example/src/main/java/org/apache/rocketmq/example/tracemessage/TracePushConsumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/tracemessage/TracePushConsumer.java @@ -17,30 +17,31 @@ package org.apache.rocketmq.example.tracemessage; -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 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("CID_JODIE_1",true); - consumer.subscribe("TopicTest", "*"); + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP, true); + + // 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(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.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"); diff --git a/example/src/main/java/org/apache/rocketmq/example/transaction/TransactionProducer.java b/example/src/main/java/org/apache/rocketmq/example/transaction/TransactionProducer.java index 75c805b9698..5973c3c306c 100644 --- a/example/src/main/java/org/apache/rocketmq/example/transaction/TransactionProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/transaction/TransactionProducer.java @@ -26,21 +26,27 @@ import java.io.UnsupportedEncodingException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadFactory; 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("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; - } + TransactionMQProducer producer = new TransactionMQProducer(PRODUCER_GROUP); + + // 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); @@ -48,10 +54,10 @@ public Thread newThread(Runnable r) { producer.start(); String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"}; - for (int i = 0; i < 10; i++) { + for (int i = 0; i < MESSAGE_COUNT; i++) { try { Message msg = - new Message("TopicTest1234", tags[i % tags.length], "KEY" + i, + 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); diff --git a/filter/BUILD.bazel b/filter/BUILD.bazel new file mode 100644 index 00000000000..048c3bdb623 --- /dev/null +++ b/filter/BUILD.bazel @@ -0,0 +1,58 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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", + "//remoting", + "//srvutil", + "@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_fastjson", + "@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", + ], +) diff --git a/filter/pom.xml b/filter/pom.xml index 80c59e89542..1c4bfdc482d 100644 --- a/filter/pom.xml +++ b/filter/pom.xml @@ -20,7 +20,7 @@ rocketmq-all org.apache.rocketmq - 4.9.4-SNAPSHOT + 5.1.3 4.0.0 @@ -28,6 +28,10 @@ rocketmq-filter rocketmq-filter ${project.version} + + ${basedir}/.. + + ${project.groupId} diff --git a/filter/src/main/java/org/apache/rocketmq/filter/FilterFactory.java b/filter/src/main/java/org/apache/rocketmq/filter/FilterFactory.java index f1e1c7d73fa..71a8b4d4bd3 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/FilterFactory.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/FilterFactory.java @@ -27,7 +27,7 @@ public class FilterFactory { public static final FilterFactory INSTANCE = new FilterFactory(); - protected static final Map FILTER_SPI_HOLDER = new HashMap(4); + protected static final Map FILTER_SPI_HOLDER = new HashMap<>(4); static { FilterFactory.INSTANCE.register(new SqlFilter()); diff --git a/filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java b/filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java index 9a919c4441c..14fd7045b41 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java @@ -32,7 +32,7 @@ */ public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression { - public static final ThreadLocal CONVERT_STRING_EXPRESSIONS = new ThreadLocal(); + public static final ThreadLocal CONVERT_STRING_EXPRESSIONS = new ThreadLocal<>(); boolean convertStringExpressions = false; @@ -69,6 +69,258 @@ public static BooleanExpression createNotBetween(Expression value, Expression le 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) { diff --git a/filter/src/main/java/org/apache/rocketmq/filter/expression/UnaryExpression.java b/filter/src/main/java/org/apache/rocketmq/filter/expression/UnaryExpression.java index 7f18ddd5413..7a62624b514 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/expression/UnaryExpression.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/expression/UnaryExpression.java @@ -82,7 +82,7 @@ public static BooleanExpression createInExpression(PropertyExpression right, Lis } else if (elements.size() < 5) { t = elements; } else { - t = new HashSet(elements); + t = new HashSet<>(elements); } final Collection inList = t; diff --git a/filter/src/main/java/org/apache/rocketmq/filter/parser/ParseException.java b/filter/src/main/java/org/apache/rocketmq/filter/parser/ParseException.java index 0a327bea1c0..39762509e0d 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/parser/ParseException.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/ParseException.java @@ -202,4 +202,4 @@ static String add_escapes(String str) { } } -/* JavaCC - OriginalChecksum=4c829b0daa2c9af00ddafe2441eb9097 (do not edit this line) */ +/* JavaCC - OriginalChecksum=60cf9c227a487e4be49599bc903f0a6a (do not edit this line) */ diff --git a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java index 4ea7dcfd428..0aaf2bc01a0 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java @@ -41,10 +41,10 @@ public class SelectorParser implements SelectorParserConstants { private static final Cache PARSE_CACHE = CacheBuilder.newBuilder().maximumSize(100).build(); - // private static final String CONVERT_STRING_EXPRESSIONS_PREFIX = "convert_string_expressions:"; +// private static final String CONVERT_STRING_EXPRESSIONS_PREFIX = "convert_string_expressions:"; public static BooleanExpression parse(String sql) throws MQFilterException { - // sql = "("+sql+")"; +// sql = "("+sql+")"; Object result = PARSE_CACHE.getIfPresent(sql); if (result instanceof MQFilterException) { throw (MQFilterException) result; @@ -52,14 +52,14 @@ public static BooleanExpression parse(String sql) throws MQFilterException { 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); - // } +// 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 { @@ -71,9 +71,9 @@ public static BooleanExpression parse(String sql) throws MQFilterException { throw t; } finally { ComparisonExpression.CONVERT_STRING_EXPRESSIONS.remove(); - // if( convertStringExpressions ) { - // ComparisonExpression.CONVERT_STRING_EXPRESSIONS.remove(); - // } +// if( convertStringExpressions ) { +// ComparisonExpression.CONVERT_STRING_EXPRESSIONS.remove(); +// } } } } @@ -108,14 +108,13 @@ private BooleanExpression asBooleanExpression(Expression value) throws ParseExce } // ---------------------------------------------------------------------------- - // Grammer + // Grammar // ---------------------------------------------------------------------------- final public BooleanExpression JmsSelector() throws ParseException { Expression left = null; left = orExpression(); { - if (true) - return asBooleanExpression(left); + if (true) return asBooleanExpression(left); } throw new Error("Missing return statement in function"); } @@ -138,8 +137,7 @@ final public Expression orExpression() throws ParseException { left = LogicExpression.createOR(asBooleanExpression(left), asBooleanExpression(right)); } { - if (true) - return left; + if (true) return left; } throw new Error("Missing return statement in function"); } @@ -162,8 +160,7 @@ final public Expression andExpression() throws ParseException { left = LogicExpression.createAND(asBooleanExpression(left), asBooleanExpression(right)); } { - if (true) - return left; + if (true) return left; } throw new Error("Missing return statement in function"); } @@ -176,21 +173,21 @@ final public Expression equalityExpression() throws ParseException { while (true) { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { case IS: - case 22: - case 23: + case 25: + case 26: break; default: jjLa1[2] = jjGen; break label_3; } switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { - case 22: - jj_consume_token(22); + case 25: + jj_consume_token(25); right = comparisonExpression(); left = ComparisonExpression.createEqual(left, right); break; - case 23: - jj_consume_token(23); + case 26: + jj_consume_token(26); right = comparisonExpression(); left = ComparisonExpression.createNotEqual(left, right); break; @@ -217,8 +214,7 @@ final public Expression equalityExpression() throws ParseException { } } { - if (true) - return left; + if (true) return left; } throw new Error("Missing return statement in function"); } @@ -238,111 +234,161 @@ final public Expression comparisonExpression() throws ParseException { case NOT: case BETWEEN: case IN: - case 24: - case 25: - case 26: + 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 24: - jj_consume_token(24); + case 27: + jj_consume_token(27); right = unaryExpr(); left = ComparisonExpression.createGreaterThan(left, right); break; - case 25: - jj_consume_token(25); + case 28: + jj_consume_token(28); right = unaryExpr(); left = ComparisonExpression.createGreaterThanEqual(left, right); break; - case 26: - jj_consume_token(26); + case 29: + jj_consume_token(29); right = unaryExpr(); left = ComparisonExpression.createLessThan(left, right); break; - case 27: - jj_consume_token(27); + case 30: + jj_consume_token(30); right = unaryExpr(); left = ComparisonExpression.createLessThanEqual(left, right); break; - case BETWEEN: - jj_consume_token(BETWEEN); - low = unaryExpr(); - jj_consume_token(AND); - high = unaryExpr(); - left = ComparisonExpression.createBetween(left, low, high); + 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(BETWEEN); - low = unaryExpr(); - jj_consume_token(AND); - high = unaryExpr(); - left = ComparisonExpression.createNotBetween(left, low, high); + jj_consume_token(CONTAINS); + t = stringLitteral(); + left = ComparisonExpression.createNotContains(left, t); } else { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { - case IN: - jj_consume_token(IN); - jj_consume_token(28); + case STARTSWITH: + jj_consume_token(STARTSWITH); t = stringLitteral(); - list = new ArrayList(); - list.add(t); - label_5: - while (true) { - switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { - case 29: - break; - default: - jjLa1[6] = jjGen; - break label_5; - } - jj_consume_token(29); - t = stringLitteral(); - list.add(t); - } - jj_consume_token(30); - left = ComparisonExpression.createInFilter(left, list); + left = ComparisonExpression.createStartsWith(left, t); break; default: jjLa1[9] = jjGen; if (jj_2_3(2)) { jj_consume_token(NOT); - jj_consume_token(IN); - jj_consume_token(28); + jj_consume_token(STARTSWITH); t = stringLitteral(); - list = new ArrayList(); - list.add(t); - label_6: - while (true) { - switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { - case 29: - break; - default: - jjLa1[7] = jjGen; - break label_6; - } - jj_consume_token(29); - t = stringLitteral(); - list.add(t); - } - jj_consume_token(30); - left = ComparisonExpression.createNotInFilter(left, list); + left = ComparisonExpression.createNotStartsWith(left, t); } else { - jj_consume_token(-1); - throw new ParseException(); + 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(); + } + } + } + } + } + } } } } } } { - if (true) - return left; + if (true) return left; } throw new Error("Missing return statement in function"); } @@ -350,13 +396,13 @@ final public Expression comparisonExpression() throws ParseException { final public Expression unaryExpr() throws ParseException { String s = null; Expression left = null; - if (jj_2_4(2147483647)) { - jj_consume_token(31); + if (jj_2_7(2147483647)) { + jj_consume_token(34); left = unaryExpr(); } else { switch ((jjNtk == -1) ? jj_ntk() : jjNtk) { - case 32: - jj_consume_token(32); + case 35: + jj_consume_token(35); left = unaryExpr(); left = UnaryExpression.createNegate(left); break; @@ -372,18 +418,17 @@ final public Expression unaryExpr() throws ParseException { case FLOATING_POINT_LITERAL: case STRING_LITERAL: case ID: - case 28: + case 31: left = primaryExpr(); break; default: - jjLa1[10] = jjGen; + jjLa1[13] = jjGen; jj_consume_token(-1); throw new ParseException(); } } { - if (true) - return left; + if (true) return left; } throw new Error("Missing return statement in function"); } @@ -402,19 +447,18 @@ final public Expression primaryExpr() throws ParseException { case ID: left = variable(); break; - case 28: - jj_consume_token(28); + case 31: + jj_consume_token(31); left = orExpression(); - jj_consume_token(30); + jj_consume_token(33); break; default: - jjLa1[11] = jjGen; + jjLa1[14] = jjGen; jj_consume_token(-1); throw new ParseException(); } { - if (true) - return left; + if (true) return left; } throw new Error("Missing return statement in function"); } @@ -449,13 +493,12 @@ final public ConstantExpression literal() throws ParseException { left = BooleanConstantExpression.NULL; break; default: - jjLa1[12] = jjGen; + jjLa1[15] = jjGen; jj_consume_token(-1); throw new ParseException(); } { - if (true) - return left; + if (true) return left; } throw new Error("Missing return statement in function"); } @@ -469,13 +512,12 @@ final public String stringLitteral() throws ParseException { String image = t.image; for (int i = 1; i < image.length() - 1; i++) { char c = image.charAt(i); - if (c == '\'') + if (c == '\u005c'') i++; rc.append(c); } { - if (true) - return rc.toString(); + if (true) return rc.toString(); } throw new Error("Missing return statement in function"); } @@ -486,8 +528,7 @@ final public PropertyExpression variable() throws ParseException { t = jj_consume_token(ID); left = new PropertyExpression(t.image); { - if (true) - return left; + if (true) return left; } throw new Error("Missing return statement in function"); } @@ -540,94 +581,53 @@ private boolean jj_2_4(int xla) { } } - 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_43() { - if (jj_scan_token(29)) - return true; - if (jj_3R_27()) - return true; - return false; - } - - private boolean jj_3R_24() { - if (jj_scan_token(NULL)) - 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)) + private boolean jj_2_5(int xla) { + jjLa = xla; + jjLastpos = jjScanpos = token; + try { + return !jj_3_5(); + } catch (LookaheadSuccess ls) { return true; - return false; + } finally { + jj_save(4, xla); + } } - private boolean jj_3_1() { - if (jj_scan_token(IS)) - return true; - if (jj_scan_token(NULL)) + private boolean jj_2_6(int xla) { + jjLa = xla; + jjLastpos = jjScanpos = token; + try { + return !jj_3_6(); + } catch (LookaheadSuccess ls) { return true; - return false; + } finally { + jj_save(5, xla); + } } - private boolean jj_3R_23() { - if (jj_scan_token(FALSE)) + private boolean jj_2_7(int xla) { + jjLa = xla; + jjLastpos = jjScanpos = token; + try { + return !jj_3_7(); + } catch (LookaheadSuccess ls) { return true; - return false; + } finally { + jj_save(6, xla); + } } private boolean jj_3R_34() { - if (jj_scan_token(23)) - return true; - if (jj_3R_30()) - return true; + if (jj_scan_token(26)) return true; + if (jj_3R_30()) return true; return false; } - private boolean jj_3R_22() { - if (jj_scan_token(TRUE)) - return true; - return false; - } - - private boolean jj_3_3() { - if (jj_scan_token(NOT)) - return true; - if (jj_scan_token(IN)) - return true; - if (jj_scan_token(28)) - return true; - if (jj_3R_27()) - return true; - Token xsp; - while (true) { - xsp = jjScanpos; - if (jj_3R_43()) { - jjScanpos = xsp; - break; - } - } - if (jj_scan_token(30)) - return true; + 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; } @@ -640,8 +640,7 @@ private boolean jj_3R_31() { jjScanpos = xsp; if (jj_3_1()) { jjScanpos = xsp; - if (jj_3R_35()) - return true; + if (jj_3R_35()) return true; } } } @@ -649,133 +648,134 @@ private boolean jj_3R_31() { } private boolean jj_3R_33() { - if (jj_scan_token(22)) - return true; - if (jj_3R_30()) - return true; + if (jj_scan_token(25)) return true; + if (jj_3R_30()) return true; return false; } - private boolean jj_3R_42() { - if (jj_scan_token(29)) - return true; - if (jj_3R_27()) - return true; + 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_21() { - if (jj_scan_token(FLOATING_POINT_LITERAL)) - return true; + 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_20() { - if (jj_scan_token(DECIMAL_LITERAL)) - return true; + private boolean jj_3R_14() { + if (jj_3R_17()) return true; return false; } - private boolean jj_3R_28() { - if (jj_3R_30()) - return true; + 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; - while (true) { - xsp = jjScanpos; - if (jj_3R_31()) { + xsp = jjScanpos; + if (jj_3R_13()) { + jjScanpos = xsp; + if (jj_3R_14()) { jjScanpos = xsp; - break; + if (jj_3R_15()) return true; } } return false; } - private boolean jj_3R_41() { - if (jj_scan_token(IN)) - return true; - if (jj_scan_token(28)) - return true; - if (jj_3R_27()) - return true; + private boolean jj_3R_28() { + if (jj_3R_30()) return true; Token xsp; while (true) { xsp = jjScanpos; - if (jj_3R_42()) { + if (jj_3R_31()) { jjScanpos = xsp; break; } } - if (jj_scan_token(30)) - return true; return false; } - private boolean jj_3R_19() { - if (jj_3R_27()) - return true; + 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; + if (jj_scan_token(AND)) return true; + if (jj_3R_28()) 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; - } - } - } - } - } + 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(BETWEEN)) - return true; - if (jj_3R_7()) - return true; - if (jj_scan_token(AND)) - return true; - if (jj_3R_7()) - return true; + 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(BETWEEN)) - return true; - if (jj_3R_7()) - return true; - if (jj_scan_token(AND)) - return true; - if (jj_3R_7()) - return true; + 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; + if (jj_3R_28()) return true; Token xsp; while (true) { xsp = jjScanpos; @@ -787,77 +787,66 @@ private boolean jj_3R_25() { return false; } - private boolean jj_3R_39() { - if (jj_scan_token(27)) - return true; - if (jj_3R_7()) - return true; + private boolean jj_3R_8() { + if (jj_scan_token(34)) return true; + if (jj_3R_7()) return true; return false; } - private boolean jj_3R_15() { - if (jj_scan_token(28)) - return true; - if (jj_3R_18()) - return true; - if (jj_scan_token(30)) - return true; + private boolean jj_3R_39() { + if (jj_scan_token(30)) return true; + if (jj_3R_7()) return true; return false; } - private boolean jj_3R_14() { - if (jj_3R_17()) - return true; + 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(26)) - return true; - if (jj_3R_7()) - return true; + if (jj_scan_token(29)) return true; + if (jj_3R_7()) return true; return false; } - private boolean jj_3R_13() { - if (jj_3R_16()) - return true; + 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; + if (jj_scan_token(OR)) return true; + if (jj_3R_25()) return true; return false; } - private boolean jj_3R_17() { - if (jj_scan_token(ID)) - return true; + private boolean jj_3R_37() { + if (jj_scan_token(28)) return true; + if (jj_3R_7()) return true; return false; } - private boolean jj_3R_37() { - if (jj_scan_token(25)) - return true; - if (jj_3R_7()) - return true; + private boolean jj_3R_24() { + if (jj_scan_token(NULL)) 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; - } - } + private boolean jj_3R_36() { + if (jj_scan_token(27)) return true; + if (jj_3R_7()) return true; return false; } @@ -878,8 +867,25 @@ private boolean jj_3R_32() { jjScanpos = xsp; if (jj_3R_41()) { jjScanpos = xsp; - if (jj_3_3()) - return true; + 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; + } + } + } + } + } + } } } } @@ -890,83 +896,137 @@ private boolean jj_3R_32() { return false; } - private boolean jj_3R_36() { - if (jj_scan_token(24)) - return true; - if (jj_3R_7()) - return true; + private boolean jj_3R_23() { + if (jj_scan_token(FALSE)) return true; return false; } - private boolean jj_3R_11() { - if (jj_3R_12()) - return true; + 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_18() { - if (jj_3R_25()) - return true; + 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_26()) { + if (jj_3R_46()) { jjScanpos = xsp; break; } } + if (jj_scan_token(33)) return true; return false; } - private boolean jj_3_4() { - if (jj_scan_token(31)) - return true; - if (jj_3R_7()) - return true; + private boolean jj_3R_45() { + if (jj_scan_token(32)) 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; + 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_9() { - if (jj_scan_token(32)) - return true; - if (jj_3R_7()) - return true; + private boolean jj_3R_21() { + if (jj_scan_token(FLOATING_POINT_LITERAL)) return true; return false; } - private boolean jj_3R_27() { - if (jj_scan_token(STRING_LITERAL)) - return true; + private boolean jj_3R_20() { + if (jj_scan_token(DECIMAL_LITERAL)) return true; return false; } - private boolean jj_3R_30() { - if (jj_3R_7()) - return true; + 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_32()) { + if (jj_3R_45()) { jjScanpos = xsp; break; } } + if (jj_scan_token(33)) return true; return false; } - private boolean jj_3R_8() { - if (jj_scan_token(31)) - return true; - if (jj_3R_7()) - return true; + 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; } @@ -987,7 +1047,7 @@ private boolean jj_3R_8() { private Token jjScanpos, jjLastpos; private int jjLa; private int jjGen; - final private int[] jjLa1 = new int[13]; + final private int[] jjLa1 = new int[16]; static private int[] jjLa10; static private int[] jjLa11; @@ -997,16 +1057,14 @@ private boolean jj_3R_8() { } private static void jj_la1_init_0() { - jjLa10 = new int[] { - 0x400, 0x200, 0xc10000, 0xc00000, 0x10000, 0xf001900, 0x20000000, 0x20000000, 0xf000800, - 0x1000, 0x1036e100, 0x1036e000, 0x16e000}; + 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, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0}; + 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[4]; + final private JJCalls[] jj2Rtns = new JJCalls[7]; private boolean jjRescan = false; private int jjGc = 0; @@ -1030,10 +1088,8 @@ public SelectorParser(java.io.InputStream stream, String encoding) { token = new Token(); jjNtk = -1; jjGen = 0; - for (int i = 0; i < 13; i++) - jjLa1[i] = -1; - for (int i = 0; i < jj2Rtns.length; i++) - jj2Rtns[i] = new JJCalls(); + for (int i = 0; i < 16; i++) jjLa1[i] = -1; + for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls(); } /** @@ -1056,10 +1112,8 @@ public void ReInit(java.io.InputStream stream, String encoding) { token = new Token(); jjNtk = -1; jjGen = 0; - for (int i = 0; i < 13; i++) - jjLa1[i] = -1; - for (int i = 0; i < jj2Rtns.length; i++) - jj2Rtns[i] = new JJCalls(); + for (int i = 0; i < 16; i++) jjLa1[i] = -1; + for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls(); } /** @@ -1071,10 +1125,8 @@ public SelectorParser(java.io.Reader stream) { token = new Token(); jjNtk = -1; jjGen = 0; - for (int i = 0; i < 13; i++) - jjLa1[i] = -1; - for (int i = 0; i < jj2Rtns.length; i++) - jj2Rtns[i] = new JJCalls(); + for (int i = 0; i < 16; i++) jjLa1[i] = -1; + for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls(); } /** @@ -1086,10 +1138,8 @@ public void ReInit(java.io.Reader stream) { token = new Token(); jjNtk = -1; jjGen = 0; - for (int i = 0; i < 13; i++) - jjLa1[i] = -1; - for (int i = 0; i < jj2Rtns.length; i++) - jj2Rtns[i] = new JJCalls(); + for (int i = 0; i < 16; i++) jjLa1[i] = -1; + for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls(); } /** @@ -1100,10 +1150,8 @@ public SelectorParser(SelectorParserTokenManager tm) { token = new Token(); jjNtk = -1; jjGen = 0; - for (int i = 0; i < 13; i++) - jjLa1[i] = -1; - for (int i = 0; i < jj2Rtns.length; i++) - jj2Rtns[i] = new JJCalls(); + for (int i = 0; i < 16; i++) jjLa1[i] = -1; + for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls(); } /** @@ -1114,18 +1162,14 @@ public void ReInit(SelectorParserTokenManager tm) { token = new Token(); jjNtk = -1; jjGen = 0; - for (int i = 0; i < 13; i++) - jjLa1[i] = -1; - for (int i = 0; i < jj2Rtns.length; i++) - jj2Rtns[i] = new JJCalls(); + 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(); + if ((oldToken = token).next != null) token = token.next; + else token = token.next = tokenSource.getNextToken(); jjNtk = -1; if (token.kind == kind) { jjGen++; @@ -1134,8 +1178,7 @@ private Token jj_consume_token(int kind) throws ParseException { for (int i = 0; i < jj2Rtns.length; i++) { JJCalls c = jj2Rtns[i]; while (c != null) { - if (c.gen < jjGen) - c.first = null; + if (c.gen < jjGen) c.first = null; c = c.next; } } @@ -1170,24 +1213,20 @@ private boolean jj_scan_token(int kind) { i++; tok = tok.next; } - if (tok != null) - jj_add_error_token(kind, i); + if (tok != null) jj_add_error_token(kind, i); } - if (jjScanpos.kind != kind) - return true; - if (jjLa == 0 && jjScanpos == jjLastpos) - throw jjLs; + 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(); + if (token.next != null) token = token.next; + else token = token.next = tokenSource.getNextToken(); jjNtk = -1; jjGen++; return token; @@ -1199,10 +1238,8 @@ final public Token getNextToken() { 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(); + if (t.next != null) t = t.next; + else t = t.next = tokenSource.getNextToken(); } return t; } @@ -1214,15 +1251,14 @@ private int jj_ntk() { return jjNtk = jjNt.kind; } - private java.util.List jjExpentries = new java.util.ArrayList(); + 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 >= 100) return; if (pos == jjEndpos + 1) { jjLasttokens[jjEndpos++] = kind; } else if (jjEndpos != 0) { @@ -1230,21 +1266,22 @@ private void jj_add_error_token(int kind, int pos) { for (int i = 0; i < jjEndpos; i++) { jjExpentry[i] = jjLasttokens[i]; } - jj_entries_loop: + 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]) { - continue jj_entries_loop; + exists = false; + break; } } - jjExpentries.add(jjExpentry); - break jj_entries_loop; + if (exists) break; } } - if (pos != 0) - jjLasttokens[(jjEndpos = pos) - 1] = kind; + if (!exists) jjExpentries.add(jjExpentry); + if (pos != 0) jjLasttokens[(jjEndpos = pos) - 1] = kind; } } @@ -1253,12 +1290,12 @@ private void jj_add_error_token(int kind, int pos) { */ public ParseException generateParseException() { jjExpentries.clear(); - boolean[] la1tokens = new boolean[33]; + boolean[] la1tokens = new boolean[36]; if (jjKind >= 0) { la1tokens[jjKind] = true; jjKind = -1; } - for (int i = 0; i < 13; i++) { + for (int i = 0; i < 16; i++) { if (jjLa1[i] == jjGen) { for (int j = 0; j < 32; j++) { if ((jjLa10[i] & (1 << j)) != 0) { @@ -1270,7 +1307,7 @@ public ParseException generateParseException() { } } } - for (int i = 0; i < 33; i++) { + for (int i = 0; i < 36; i++) { if (la1tokens[i]) { jjExpentry = new int[1]; jjExpentry[0] = i; @@ -1301,7 +1338,7 @@ final public void disable_tracing() { private void jj_rescan_token() { jjRescan = true; - for (int i = 0; i < 4; i++) { + for (int i = 0; i < 7; i++) { try { JJCalls p = jj2Rtns[i]; do { @@ -1321,11 +1358,19 @@ private void jj_rescan_token() { 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); + } while (p != null); } catch (LookaheadSuccess ls) { } } diff --git a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.jj b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.jj index b533ac1778d..f2636969276 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.jj +++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.jj @@ -53,7 +53,6 @@ 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 org.apache.rocketmq.filter.util.LRUCache; import java.io.StringReader; import java.util.ArrayList; @@ -170,6 +169,9 @@ TOKEN [IGNORE_CASE] : | < FALSE : "FALSE" > | < NULL : "NULL" > | < IS : "IS" > + | < CONTAINS : "CONTAINS"> + | < STARTSWITH : "STARTSWITH"> + | < ENDSWITH : "ENDSWITH"> } /* Literals */ @@ -193,7 +195,7 @@ TOKEN [IGNORE_CASE] : } // ---------------------------------------------------------------------------- -// Grammer +// Grammar // ---------------------------------------------------------------------------- BooleanExpression JmsSelector() : { @@ -322,6 +324,39 @@ Expression comparisonExpression() : { 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() { diff --git a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserConstants.java b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserConstants.java index 915658ca60b..8f228be8bd3 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserConstants.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserConstants.java @@ -75,23 +75,35 @@ public interface SelectorParserConstants { /** * RegularExpression Id. */ - int DECIMAL_LITERAL = 17; + int CONTAINS = 17; /** * RegularExpression Id. */ - int FLOATING_POINT_LITERAL = 18; + int STARTSWITH = 18; /** * RegularExpression Id. */ - int EXPONENT = 19; + int ENDSWITH = 19; /** * RegularExpression Id. */ - int STRING_LITERAL = 20; + int DECIMAL_LITERAL = 20; /** * RegularExpression Id. */ - int ID = 21; + int FLOATING_POINT_LITERAL = 21; + /** + * RegularExpression Id. + */ + int EXPONENT = 22; + /** + * RegularExpression Id. + */ + int STRING_LITERAL = 23; + /** + * RegularExpression Id. + */ + int ID = 24; /** * Lexical state. @@ -119,6 +131,9 @@ public interface SelectorParserConstants { "\"FALSE\"", "\"NULL\"", "\"IS\"", + "\"CONTAINS\"", + "\"STARTSWITH\"", + "\"ENDSWITH\"", "", "", "", diff --git a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserTokenManager.java b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserTokenManager.java index b5bac982405..6d9b8551730 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserTokenManager.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserTokenManager.java @@ -59,33 +59,37 @@ private int jjMoveStringLiteralDfa0_0() { jjmatchedKind = 1; return jjMoveNfa_0(5, 0); case 40: - jjmatchedKind = 28; + jjmatchedKind = 31; return jjMoveNfa_0(5, 0); case 41: - jjmatchedKind = 30; + jjmatchedKind = 33; return jjMoveNfa_0(5, 0); case 43: - jjmatchedKind = 31; + jjmatchedKind = 34; return jjMoveNfa_0(5, 0); case 44: - jjmatchedKind = 29; + jjmatchedKind = 32; return jjMoveNfa_0(5, 0); case 45: - jjmatchedKind = 32; + jjmatchedKind = 35; return jjMoveNfa_0(5, 0); case 60: - jjmatchedKind = 26; - return jjMoveStringLiteralDfa1_0(0x8800000L); + jjmatchedKind = 29; + return jjMoveStringLiteralDfa1_0(0x44000000L); case 61: - jjmatchedKind = 22; + jjmatchedKind = 25; return jjMoveNfa_0(5, 0); case 62: - jjmatchedKind = 24; - return jjMoveStringLiteralDfa1_0(0x2000000L); + 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: @@ -94,12 +98,18 @@ private int jjMoveStringLiteralDfa0_0() { 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: @@ -108,6 +118,8 @@ private int jjMoveStringLiteralDfa0_0() { return jjMoveStringLiteralDfa1_0(0x8100L); case 111: return jjMoveStringLiteralDfa1_0(0x400L); + case 115: + return jjMoveStringLiteralDfa1_0(0x40000L); case 116: return jjMoveStringLiteralDfa1_0(0x2000L); default: @@ -123,17 +135,17 @@ private int jjMoveStringLiteralDfa1_0(long active0) { } switch (curChar) { case 61: - if ((active0 & 0x2000000L) != 0L) { - jjmatchedKind = 25; + if ((active0 & 0x10000000L) != 0L) { + jjmatchedKind = 28; jjmatchedPos = 1; - } else if ((active0 & 0x8000000L) != 0L) { - jjmatchedKind = 27; + } else if ((active0 & 0x40000000L) != 0L) { + jjmatchedKind = 30; jjmatchedPos = 1; } break; case 62: - if ((active0 & 0x800000L) != 0L) { - jjmatchedKind = 23; + if ((active0 & 0x4000000L) != 0L) { + jjmatchedKind = 26; jjmatchedPos = 1; } break; @@ -146,9 +158,9 @@ private int jjMoveStringLiteralDfa1_0(long active0) { jjmatchedKind = 12; jjmatchedPos = 1; } - return jjMoveStringLiteralDfa2_0(active0, 0x200L); + return jjMoveStringLiteralDfa2_0(active0, 0x80200L); case 79: - return jjMoveStringLiteralDfa2_0(active0, 0x100L); + return jjMoveStringLiteralDfa2_0(active0, 0x20100L); case 82: if ((active0 & 0x400L) != 0L) { jjmatchedKind = 10; @@ -161,6 +173,8 @@ private int jjMoveStringLiteralDfa1_0(long active0) { jjmatchedPos = 1; } break; + case 84: + return jjMoveStringLiteralDfa2_0(active0, 0x40000L); case 85: return jjMoveStringLiteralDfa2_0(active0, 0x8000L); case 97: @@ -172,9 +186,9 @@ private int jjMoveStringLiteralDfa1_0(long active0) { jjmatchedKind = 12; jjmatchedPos = 1; } - return jjMoveStringLiteralDfa2_0(active0, 0x200L); + return jjMoveStringLiteralDfa2_0(active0, 0x80200L); case 111: - return jjMoveStringLiteralDfa2_0(active0, 0x100L); + return jjMoveStringLiteralDfa2_0(active0, 0x20100L); case 114: if ((active0 & 0x400L) != 0L) { jjmatchedKind = 10; @@ -187,6 +201,8 @@ private int jjMoveStringLiteralDfa1_0(long active0) { jjmatchedPos = 1; } break; + case 116: + return jjMoveStringLiteralDfa2_0(active0, 0x40000L); case 117: return jjMoveStringLiteralDfa2_0(active0, 0x8000L); default: @@ -204,14 +220,18 @@ private int jjMoveStringLiteralDfa2_0(long old0, long active0) { return jjMoveNfa_0(5, 1); } switch (curChar) { + case 65: + return jjMoveStringLiteralDfa3_0(active0, 0x40000L); case 68: if ((active0 & 0x200L) != 0L) { jjmatchedKind = 9; jjmatchedPos = 2; } - break; + 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; @@ -220,14 +240,18 @@ private int jjMoveStringLiteralDfa2_0(long old0, long active0) { 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; } - break; + 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; @@ -263,8 +287,12 @@ private int jjMoveStringLiteralDfa3_0(long old0, long active0) { jjmatchedPos = 3; } break; + case 82: + return jjMoveStringLiteralDfa4_0(active0, 0x40000L); case 83: - return jjMoveStringLiteralDfa4_0(active0, 0x4000L); + return jjMoveStringLiteralDfa4_0(active0, 0x84000L); + case 84: + return jjMoveStringLiteralDfa4_0(active0, 0x20000L); case 87: return jjMoveStringLiteralDfa4_0(active0, 0x800L); case 101: @@ -279,8 +307,12 @@ private int jjMoveStringLiteralDfa3_0(long old0, long active0) { jjmatchedPos = 3; } break; + case 114: + return jjMoveStringLiteralDfa4_0(active0, 0x40000L); case 115: - return jjMoveStringLiteralDfa4_0(active0, 0x4000L); + return jjMoveStringLiteralDfa4_0(active0, 0x84000L); + case 116: + return jjMoveStringLiteralDfa4_0(active0, 0x20000L); case 119: return jjMoveStringLiteralDfa4_0(active0, 0x800L); default: @@ -298,18 +330,30 @@ private int jjMoveStringLiteralDfa4_0(long old0, long active0) { 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; } @@ -327,8 +371,16 @@ private int jjMoveStringLiteralDfa5_0(long old0, long active0) { 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; } @@ -349,19 +401,116 @@ private int jjMoveStringLiteralDfa6_0(long old0, long active0) { jjmatchedKind = 11; jjmatchedPos = 6; } - break; + 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; } - break; + 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 }; @@ -396,8 +545,8 @@ private int jjMoveNfa_0(int startState, int curPos) { if ((0x3ff000000000000L & l) != 0L) jjCheckNAddStates(0, 3); else if (curChar == 36) { - if (kind > 21) - kind = 21; + if (kind > 24) + kind = 24; jjCheckNAdd(28); } else if (curChar == 39) jjCheckNAddStates(4, 6); @@ -408,12 +557,12 @@ else if (curChar == 47) else if (curChar == 45) jjstateSet[jjnewStateCnt++] = 0; if ((0x3fe000000000000L & l) != 0L) { - if (kind > 17) - kind = 17; + if (kind > 20) + kind = 20; jjCheckNAddTwoStates(15, 16); } else if (curChar == 48) { - if (kind > 17) - kind = 17; + if (kind > 20) + kind = 20; } break; case 0: @@ -465,21 +614,21 @@ else if (curChar == 45) jjstateSet[jjnewStateCnt++] = 6; break; case 13: - if (curChar == 48 && kind > 17) - kind = 17; + if (curChar == 48 && kind > 20) + kind = 20; break; case 14: if ((0x3fe000000000000L & l) == 0L) break; - if (kind > 17) - kind = 17; + if (kind > 20) + kind = 20; jjCheckNAddTwoStates(15, 16); break; case 15: if ((0x3ff000000000000L & l) == 0L) break; - if (kind > 17) - kind = 17; + if (kind > 20) + kind = 20; jjCheckNAddTwoStates(15, 16); break; case 17: @@ -489,8 +638,8 @@ else if (curChar == 45) case 18: if ((0x3ff000000000000L & l) == 0L) break; - if (kind > 18) - kind = 18; + if (kind > 21) + kind = 21; jjCheckNAddTwoStates(18, 19); break; case 20: @@ -500,8 +649,8 @@ else if (curChar == 45) case 21: if ((0x3ff000000000000L & l) == 0L) break; - if (kind > 18) - kind = 18; + if (kind > 21) + kind = 21; jjCheckNAdd(21); break; case 22: @@ -518,21 +667,21 @@ else if (curChar == 45) jjCheckNAddStates(4, 6); break; case 26: - if (curChar == 39 && kind > 20) - kind = 20; + if (curChar == 39 && kind > 23) + kind = 23; break; case 27: if (curChar != 36) break; - if (kind > 21) - kind = 21; + if (kind > 24) + kind = 24; jjCheckNAdd(28); break; case 28: if ((0x3ff001000000000L & l) == 0L) break; - if (kind > 21) - kind = 21; + if (kind > 24) + kind = 24; jjCheckNAdd(28); break; case 29: @@ -546,15 +695,15 @@ else if (curChar == 45) case 31: if (curChar != 46) break; - if (kind > 18) - kind = 18; + if (kind > 21) + kind = 21; jjCheckNAddTwoStates(32, 33); break; case 32: if ((0x3ff000000000000L & l) == 0L) break; - if (kind > 18) - kind = 18; + if (kind > 21) + kind = 21; jjCheckNAddTwoStates(32, 33); break; case 34: @@ -564,8 +713,8 @@ else if (curChar == 45) case 35: if ((0x3ff000000000000L & l) == 0L) break; - if (kind > 18) - kind = 18; + if (kind > 21) + kind = 21; jjCheckNAdd(35); break; case 36: @@ -579,15 +728,14 @@ else if (curChar == 45) case 39: if ((0x3ff000000000000L & l) == 0L) break; - if (kind > 18) - kind = 18; + if (kind > 21) + kind = 21; jjCheckNAdd(39); break; default: break; } - } - while (i != startsAt); + } while (i != startsAt); } else if (curChar < 128) { long l = 1L << (curChar & 077); do { @@ -596,8 +744,8 @@ else if (curChar == 45) case 28: if ((0x7fffffe87fffffeL & l) == 0L) break; - if (kind > 21) - kind = 21; + if (kind > 24) + kind = 24; jjCheckNAdd(28); break; case 1: @@ -611,8 +759,8 @@ else if (curChar == 45) jjCheckNAddTwoStates(10, 8); break; case 16: - if ((0x100000001000L & l) != 0L && kind > 17) - kind = 17; + if ((0x100000001000L & l) != 0L && kind > 20) + kind = 20; break; case 19: if ((0x2000000020L & l) != 0L) @@ -632,8 +780,7 @@ else if (curChar == 45) default: break; } - } - while (i != startsAt); + } while (i != startsAt); } else { int hiByte = (int) (curChar >> 8); int i1 = hiByte >> 6; @@ -662,8 +809,7 @@ else if (curChar == 45) default: break; } - } - while (i != startsAt); + } while (i != startsAt); } if (kind != 0x7fffffff) { jjmatchedKind = kind; @@ -722,8 +868,8 @@ private static final boolean jjCanMove_0(int hiByte, int i1, int i2, long l1, lo */ 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, "\75", "\74\76", "\76", - "\76\75", "\74", "\74\75", "\50", "\54", "\51", "\53", "\55"}; + 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. @@ -732,7 +878,7 @@ private static final boolean jjCanMove_0(int hiByte, int i1, int i2, long l1, lo "DEFAULT", }; static final long[] JJ_TO_TOKEN = { - 0x1fff7ff01L, + 0xfffbfff01L, }; static final long[] JJ_TO_SKIP = { 0xfeL, @@ -792,8 +938,7 @@ public void ReInit(SimpleCharStream stream, int lexState) { */ 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); + throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE); else curLexState = lexState; } @@ -890,8 +1035,7 @@ public Token getNextToken() { inputStream.backup(1); errorAfter = curPos <= 1 ? "" : inputStream.GetImage(); } - throw new TokenMgrError(eofSeen, curLexState, errorLine, errorColumn, errorAfter, curChar, - TokenMgrError.LEXICAL_ERROR); + throw new TokenMgrError(eofSeen, curLexState, errorLine, errorColumn, errorAfter, curChar, TokenMgrError.LEXICAL_ERROR); } } @@ -905,8 +1049,7 @@ private void jjCheckNAdd(int state) { private void jjAddStates(int start, int end) { do { jjstateSet[jjnewStateCnt++] = JJ_NEXT_STATES[start]; - } - while (start++ != end); + } while (start++ != end); } private void jjCheckNAddTwoStates(int state1, int state2) { @@ -917,8 +1060,7 @@ private void jjCheckNAddTwoStates(int state1, int state2) { private void jjCheckNAddStates(int start, int end) { do { jjCheckNAdd(JJ_NEXT_STATES[start]); - } - while (start++ != end); + } while (start++ != end); } } diff --git a/filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java b/filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java index c10250e3bbb..b8e375e51cd 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java @@ -19,6 +19,8 @@ /* 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). @@ -331,7 +333,7 @@ public void ReInit(java.io.Reader dstream) { 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) : + new java.io.InputStreamReader(dstream, StandardCharsets.UTF_8) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); } @@ -340,7 +342,7 @@ public SimpleCharStream(java.io.InputStream dstream, String encoding, int startl */ public SimpleCharStream(java.io.InputStream dstream, int startline, int startcolumn, int buffersize) { - this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + this(new java.io.InputStreamReader(dstream, StandardCharsets.UTF_8), startline, startcolumn, buffersize); } /** @@ -379,7 +381,7 @@ public SimpleCharStream(java.io.InputStream dstream) { 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) : + new java.io.InputStreamReader(dstream, StandardCharsets.UTF_8) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); } @@ -388,7 +390,7 @@ public void ReInit(java.io.InputStream dstream, String encoding, int startline, */ public void ReInit(java.io.InputStream dstream, int startline, int startcolumn, int buffersize) { - ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + ReInit(new java.io.InputStreamReader(dstream, StandardCharsets.UTF_8), startline, startcolumn, buffersize); } /** @@ -499,4 +501,4 @@ public void adjustBeginLineColumn(int newLine, int newCol) { } } -/* JavaCC - OriginalChecksum=af79bfe4b18b4b4ea9720ffeb7e52fc5 (do not edit this line) */ +/* JavaCC - OriginalChecksum=ea3493f692d4975c1ad70c4a750107d3 (do not edit this line) */ diff --git a/filter/src/main/java/org/apache/rocketmq/filter/parser/Token.java b/filter/src/main/java/org/apache/rocketmq/filter/parser/Token.java index 8e6a48a0868..edb78800867 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/parser/Token.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/Token.java @@ -149,4 +149,4 @@ public static Token newToken(int ofKind) { } } -/* JavaCC - OriginalChecksum=6b0af88eb45a551d929d3cdd9582f827 (do not edit this line) */ +/* JavaCC - OriginalChecksum=20094f1ccfbf423c6d9e770d6a7a0188 (do not edit this line) */ diff --git a/filter/src/main/java/org/apache/rocketmq/filter/parser/TokenMgrError.java b/filter/src/main/java/org/apache/rocketmq/filter/parser/TokenMgrError.java index 0aeb27cf4ca..4a8f2c86a30 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/parser/TokenMgrError.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/TokenMgrError.java @@ -172,4 +172,4 @@ public TokenMgrError(boolean eofSeen, int lexState, int errorLine, int errorColu this(LexicalError(eofSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); } } -/* JavaCC - OriginalChecksum=e960778c8dcd73e167ed5bfddd59f288 (do not edit this line) */ +/* JavaCC - OriginalChecksum=de79709675790dcbad2e0d728aa630d1 (do not edit this line) */ diff --git a/filter/src/main/java/org/apache/rocketmq/filter/util/BloomFilter.java b/filter/src/main/java/org/apache/rocketmq/filter/util/BloomFilter.java index 9a3de6016b6..d909cc2a57c 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/util/BloomFilter.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/util/BloomFilter.java @@ -20,13 +20,14 @@ 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 = Charset.forName("UTF-8"); + public static final Charset UTF_8 = StandardCharsets.UTF_8; // as error rate, 10/100 = 0.1 private int f = 10; @@ -145,7 +146,7 @@ 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.toString()) + filterData, this) ); } hashTo(filterData.getBitPos(), bits); @@ -183,7 +184,7 @@ 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.toString()) + filterData, this) ); } return isHit(filterData.getBitPos(), bits); diff --git a/filter/src/test/java/org/apache/rocketmq/filter/ExpressionTest.java b/filter/src/test/java/org/apache/rocketmq/filter/ExpressionTest.java index 7fb606ac13b..df883458ed6 100644 --- a/filter/src/test/java/org/apache/rocketmq/filter/ExpressionTest.java +++ b/filter/src/test/java/org/apache/rocketmq/filter/ExpressionTest.java @@ -46,6 +46,372 @@ public class ExpressionTest { 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); @@ -576,7 +942,7 @@ public KeyValue(String key, Object value) { class PropertyContext implements EvaluationContext { - public Map properties = new HashMap(8); + public Map properties = new HashMap<>(8); @Override public Object get(final String name) { diff --git a/filter/src/test/java/org/apache/rocketmq/filter/ParserTest.java b/filter/src/test/java/org/apache/rocketmq/filter/ParserTest.java index 7dc2ab25468..9e6291ff10b 100644 --- a/filter/src/test/java/org/apache/rocketmq/filter/ParserTest.java +++ b/filter/src/test/java/org/apache/rocketmq/filter/ParserTest.java @@ -37,7 +37,7 @@ public class ParserTest { 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"; @@ -45,7 +45,7 @@ public class ParserTest { public void testParse_valid() { for (String expr : Arrays.asList( andExpression, orExpression, inExpression, notInExpression, betweenExpression, - equalNullExpression, notEqualNullExpression, nowExpression + equalNullExpression, notEqualNullExpression, nowExpression, containsExpression )) { try { diff --git a/filter/src/test/resources/rmq.logback-test.xml b/filter/src/test/resources/rmq.logback-test.xml new file mode 100644 index 00000000000..8695d52d57c --- /dev/null +++ b/filter/src/test/resources/rmq.logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/logging/src/main/java/org/apache/rocketmq/logging/InnerLoggerFactory.java b/logging/src/main/java/org/apache/rocketmq/logging/InnerLoggerFactory.java deleted file mode 100644 index d95245356ef..00000000000 --- a/logging/src/main/java/org/apache/rocketmq/logging/InnerLoggerFactory.java +++ /dev/null @@ -1,482 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging; - -import org.apache.rocketmq.logging.inner.Logger; - -import java.util.HashMap; -import java.util.Map; - -public class InnerLoggerFactory extends InternalLoggerFactory { - - public InnerLoggerFactory() { - doRegister(); - } - - @Override - protected InternalLogger getLoggerInstance(String name) { - return new InnerLogger(name); - } - - @Override - protected String getLoggerType() { - return LOGGER_INNER; - } - - @Override - protected void shutdown() { - Logger.getRepository().shutdown(); - } - - public static class InnerLogger implements InternalLogger { - - private Logger logger; - - public InnerLogger(String name) { - logger = Logger.getLogger(name); - } - - @Override - public String getName() { - return logger.getName(); - } - - @Override - public void debug(String var1) { - logger.debug(var1); - } - - @Override - public void debug(String var1, Throwable var2) { - logger.debug(var1, var2); - } - - @Override - public void info(String var1) { - logger.info(var1); - } - - @Override - public void info(String var1, Throwable var2) { - logger.info(var1, var2); - } - - @Override - public void warn(String var1) { - logger.warn(var1); - } - - @Override - public void warn(String var1, Throwable var2) { - logger.warn(var1, var2); - } - - @Override - public void error(String var1) { - logger.error(var1); - } - - @Override - public void error(String var1, Throwable var2) { - logger.error(var1, var2); - } - - @Override - public void debug(String var1, Object var2) { - FormattingTuple format = MessageFormatter.format(var1, var2); - logger.debug(format.getMessage(), format.getThrowable()); - } - - @Override - public void debug(String var1, Object var2, Object var3) { - FormattingTuple format = MessageFormatter.format(var1, var2, var3); - logger.debug(format.getMessage(), format.getThrowable()); - } - - @Override - public void debug(String var1, Object... var2) { - FormattingTuple format = MessageFormatter.arrayFormat(var1, var2); - logger.debug(format.getMessage(), format.getThrowable()); - } - - @Override - public void info(String var1, Object var2) { - FormattingTuple format = MessageFormatter.format(var1, var2); - logger.info(format.getMessage(), format.getThrowable()); - } - - @Override - public void info(String var1, Object var2, Object var3) { - FormattingTuple format = MessageFormatter.format(var1, var2, var3); - logger.info(format.getMessage(), format.getThrowable()); - } - - @Override - public void info(String var1, Object... var2) { - FormattingTuple format = MessageFormatter.arrayFormat(var1, var2); - logger.info(format.getMessage(), format.getThrowable()); - } - - @Override - public void warn(String var1, Object var2) { - FormattingTuple format = MessageFormatter.format(var1, var2); - logger.warn(format.getMessage(), format.getThrowable()); - } - - @Override - public void warn(String var1, Object... var2) { - FormattingTuple format = MessageFormatter.arrayFormat(var1, var2); - logger.warn(format.getMessage(), format.getThrowable()); - } - - @Override - public void warn(String var1, Object var2, Object var3) { - FormattingTuple format = MessageFormatter.format(var1, var2, var3); - logger.warn(format.getMessage(), format.getThrowable()); - } - - @Override - public void error(String var1, Object var2) { - FormattingTuple format = MessageFormatter.format(var1, var2); - logger.warn(format.getMessage(), format.getThrowable()); - } - - @Override - public void error(String var1, Object var2, Object var3) { - FormattingTuple format = MessageFormatter.format(var1, var2, var3); - logger.warn(format.getMessage(), format.getThrowable()); - } - - @Override - public void error(String var1, Object... var2) { - FormattingTuple format = MessageFormatter.arrayFormat(var1, var2); - logger.warn(format.getMessage(), format.getThrowable()); - } - - public Logger getLogger() { - return logger; - } - } - - - public static class FormattingTuple { - private String message; - private Throwable throwable; - private Object[] argArray; - - public FormattingTuple(String message) { - this(message, null, null); - } - - public FormattingTuple(String message, Object[] argArray, Throwable throwable) { - this.message = message; - this.throwable = throwable; - if (throwable == null) { - this.argArray = argArray; - } else { - this.argArray = trimmedCopy(argArray); - } - - } - - static Object[] trimmedCopy(Object[] argArray) { - if (argArray != null && argArray.length != 0) { - int trimemdLen = argArray.length - 1; - Object[] trimmed = new Object[trimemdLen]; - System.arraycopy(argArray, 0, trimmed, 0, trimemdLen); - return trimmed; - } else { - throw new IllegalStateException("non-sensical empty or null argument array"); - } - } - - public String getMessage() { - return this.message; - } - - public Object[] getArgArray() { - return this.argArray; - } - - public Throwable getThrowable() { - return this.throwable; - } - } - - public static class MessageFormatter { - - public MessageFormatter() { - } - - public static FormattingTuple format(String messagePattern, Object arg) { - return arrayFormat(messagePattern, new Object[]{arg}); - } - - public static FormattingTuple format(String messagePattern, Object arg1, Object arg2) { - return arrayFormat(messagePattern, new Object[]{arg1, arg2}); - } - - static Throwable getThrowableCandidate(Object[] argArray) { - if (argArray != null && argArray.length != 0) { - Object lastEntry = argArray[argArray.length - 1]; - return lastEntry instanceof Throwable ? (Throwable) lastEntry : null; - } else { - return null; - } - } - - public static FormattingTuple arrayFormat(String messagePattern, Object[] argArray) { - Throwable throwableCandidate = getThrowableCandidate(argArray); - if (messagePattern == null) { - return new FormattingTuple(null, argArray, throwableCandidate); - } else if (argArray == null) { - return new FormattingTuple(messagePattern); - } else { - int i = 0; - StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); - - int len; - for (len = 0; len < argArray.length; ++len) { - int j = messagePattern.indexOf("{}", i); - if (j == -1) { - if (i == 0) { - return new FormattingTuple(messagePattern, argArray, throwableCandidate); - } - - sbuf.append(messagePattern.substring(i, messagePattern.length())); - return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate); - } - - if (isEscapeDelimeter(messagePattern, j)) { - if (!isDoubleEscaped(messagePattern, j)) { - --len; - sbuf.append(messagePattern.substring(i, j - 1)); - sbuf.append('{'); - i = j + 1; - } else { - sbuf.append(messagePattern.substring(i, j - 1)); - deeplyAppendParameter(sbuf, argArray[len], null); - i = j + 2; - } - } else { - sbuf.append(messagePattern.substring(i, j)); - deeplyAppendParameter(sbuf, argArray[len], null); - i = j + 2; - } - } - - sbuf.append(messagePattern.substring(i, messagePattern.length())); - if (len < argArray.length - 1) { - return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate); - } else { - return new FormattingTuple(sbuf.toString(), argArray, null); - } - } - } - - static boolean isEscapeDelimeter(String messagePattern, int delimeterStartIndex) { - if (delimeterStartIndex == 0) { - return false; - } else { - char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1); - return potentialEscape == 92; - } - } - - static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) { - return delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == 92; - } - - private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map seenMap) { - if (o == null) { - sbuf.append("null"); - } else { - if (!o.getClass().isArray()) { - safeObjectAppend(sbuf, o); - } else if (o instanceof boolean[]) { - booleanArrayAppend(sbuf, (boolean[]) o); - } else if (o instanceof byte[]) { - byteArrayAppend(sbuf, (byte[]) o); - } else if (o instanceof char[]) { - charArrayAppend(sbuf, (char[]) o); - } else if (o instanceof short[]) { - shortArrayAppend(sbuf, (short[]) o); - } else if (o instanceof int[]) { - intArrayAppend(sbuf, (int[]) o); - } else if (o instanceof long[]) { - longArrayAppend(sbuf, (long[]) o); - } else if (o instanceof float[]) { - floatArrayAppend(sbuf, (float[]) o); - } else if (o instanceof double[]) { - doubleArrayAppend(sbuf, (double[]) o); - } else { - objectArrayAppend(sbuf, (Object[]) o, seenMap); - } - - } - } - - private static void safeObjectAppend(StringBuilder sbuf, Object o) { - try { - String t = o.toString(); - sbuf.append(t); - } catch (Throwable var3) { - System.err.println("RocketMQ InnerLogger: Failed toString() invocation on an object of type [" + o.getClass().getName() + "]"); - var3.printStackTrace(); - sbuf.append("[FAILED toString()]"); - } - - } - - private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map seenMap) { - if (seenMap == null) { - seenMap = new HashMap(); - } - sbuf.append('['); - if (!seenMap.containsKey(a)) { - seenMap.put(a, null); - int len = a.length; - - for (int i = 0; i < len; ++i) { - deeplyAppendParameter(sbuf, a[i], seenMap); - if (i != len - 1) { - sbuf.append(", "); - } - } - - seenMap.remove(a); - } else { - sbuf.append("..."); - } - - sbuf.append(']'); - } - - private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) { - sbuf.append('['); - int len = a.length; - - for (int i = 0; i < len; ++i) { - sbuf.append(a[i]); - if (i != len - 1) { - sbuf.append(", "); - } - } - - sbuf.append(']'); - } - - private static void byteArrayAppend(StringBuilder sbuf, byte[] a) { - sbuf.append('['); - int len = a.length; - - for (int i = 0; i < len; ++i) { - sbuf.append(a[i]); - if (i != len - 1) { - sbuf.append(", "); - } - } - - sbuf.append(']'); - } - - private static void charArrayAppend(StringBuilder sbuf, char[] a) { - sbuf.append('['); - int len = a.length; - - for (int i = 0; i < len; ++i) { - sbuf.append(a[i]); - if (i != len - 1) { - sbuf.append(", "); - } - } - - sbuf.append(']'); - } - - private static void shortArrayAppend(StringBuilder sbuf, short[] a) { - sbuf.append('['); - int len = a.length; - - for (int i = 0; i < len; ++i) { - sbuf.append(a[i]); - if (i != len - 1) { - sbuf.append(", "); - } - } - - sbuf.append(']'); - } - - private static void intArrayAppend(StringBuilder sbuf, int[] a) { - sbuf.append('['); - int len = a.length; - - for (int i = 0; i < len; ++i) { - sbuf.append(a[i]); - if (i != len - 1) { - sbuf.append(", "); - } - } - - sbuf.append(']'); - } - - private static void longArrayAppend(StringBuilder sbuf, long[] a) { - sbuf.append('['); - int len = a.length; - - for (int i = 0; i < len; ++i) { - sbuf.append(a[i]); - if (i != len - 1) { - sbuf.append(", "); - } - } - - sbuf.append(']'); - } - - private static void floatArrayAppend(StringBuilder sbuf, float[] a) { - sbuf.append('['); - int len = a.length; - - for (int i = 0; i < len; ++i) { - sbuf.append(a[i]); - if (i != len - 1) { - sbuf.append(", "); - } - } - - sbuf.append(']'); - } - - private static void doubleArrayAppend(StringBuilder sbuf, double[] a) { - sbuf.append('['); - int len = a.length; - - for (int i = 0; i < len; ++i) { - sbuf.append(a[i]); - if (i != len - 1) { - sbuf.append(", "); - } - } - - sbuf.append(']'); - } - } -} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/InternalLogger.java b/logging/src/main/java/org/apache/rocketmq/logging/InternalLogger.java deleted file mode 100644 index fae69dda6c5..00000000000 --- a/logging/src/main/java/org/apache/rocketmq/logging/InternalLogger.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging; - -public interface InternalLogger { - - String getName(); - - void debug(String var1); - - void debug(String var1, Object var2); - - void debug(String var1, Object var2, Object var3); - - void debug(String var1, Object... var2); - - void debug(String var1, Throwable var2); - - void info(String var1); - - void info(String var1, Object var2); - - void info(String var1, Object var2, Object var3); - - void info(String var1, Object... var2); - - void info(String var1, Throwable var2); - - void warn(String var1); - - void warn(String var1, Object var2); - - void warn(String var1, Object... var2); - - void warn(String var1, Object var2, Object var3); - - void warn(String var1, Throwable var2); - - void error(String var1); - - void error(String var1, Object var2); - - void error(String var1, Object var2, Object var3); - - void error(String var1, Object... var2); - - void error(String var1, Throwable var2); -} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/InternalLoggerFactory.java b/logging/src/main/java/org/apache/rocketmq/logging/InternalLoggerFactory.java deleted file mode 100644 index 17c56bdd110..00000000000 --- a/logging/src/main/java/org/apache/rocketmq/logging/InternalLoggerFactory.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging; - -import java.util.concurrent.ConcurrentHashMap; - -public abstract class InternalLoggerFactory { - - public static final String LOGGER_SLF4J = "slf4j"; - - public static final String LOGGER_INNER = "inner"; - - public static final String DEFAULT_LOGGER = LOGGER_SLF4J; - - private static String loggerType = null; - - private static ConcurrentHashMap loggerFactoryCache = new ConcurrentHashMap(); - - public static InternalLogger getLogger(Class clazz) { - return getLogger(clazz.getName()); - } - - public static InternalLogger getLogger(String name) { - return getLoggerFactory().getLoggerInstance(name); - } - - private static InternalLoggerFactory getLoggerFactory() { - InternalLoggerFactory internalLoggerFactory = null; - if (loggerType != null) { - internalLoggerFactory = loggerFactoryCache.get(loggerType); - } - if (internalLoggerFactory == null) { - internalLoggerFactory = loggerFactoryCache.get(DEFAULT_LOGGER); - } - if (internalLoggerFactory == null) { - internalLoggerFactory = loggerFactoryCache.get(LOGGER_INNER); - } - if (internalLoggerFactory == null) { - throw new RuntimeException("[RocketMQ] Logger init failed, please check logger"); - } - return internalLoggerFactory; - } - - public static void setCurrentLoggerType(String type) { - loggerType = type; - } - - static { - try { - new Slf4jLoggerFactory(); - } catch (Throwable e) { - //ignore - } - try { - new InnerLoggerFactory(); - } catch (Throwable e) { - //ignore - } - } - - protected void doRegister() { - String loggerType = getLoggerType(); - if (loggerFactoryCache.get(loggerType) != null) { - return; - } - loggerFactoryCache.put(loggerType, this); - } - - protected abstract void shutdown(); - - protected abstract InternalLogger getLoggerInstance(String name); - - protected abstract String getLoggerType(); -} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/Slf4jLoggerFactory.java b/logging/src/main/java/org/apache/rocketmq/logging/Slf4jLoggerFactory.java deleted file mode 100644 index 53dbc948d1d..00000000000 --- a/logging/src/main/java/org/apache/rocketmq/logging/Slf4jLoggerFactory.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Slf4jLoggerFactory extends InternalLoggerFactory { - - public Slf4jLoggerFactory() { - LoggerFactory.getILoggerFactory(); - doRegister(); - } - - @Override - protected String getLoggerType() { - return InternalLoggerFactory.LOGGER_SLF4J; - } - - @Override - protected InternalLogger getLoggerInstance(String name) { - return new Slf4jLogger(name); - } - - @Override - protected void shutdown() { - - } - - public static class Slf4jLogger implements InternalLogger { - - private Logger logger = null; - - public Slf4jLogger(String name) { - logger = LoggerFactory.getLogger(name); - } - - @Override - public String getName() { - return logger.getName(); - } - - @Override - public void debug(String s) { - logger.debug(s); - } - - @Override - public void debug(String s, Object o) { - logger.debug(s, o); - } - - @Override - public void debug(String s, Object o, Object o1) { - logger.debug(s, o, o1); - } - - @Override - public void debug(String s, Object... objects) { - logger.debug(s, objects); - } - - @Override - public void debug(String s, Throwable throwable) { - logger.debug(s, throwable); - } - - @Override - public void info(String s) { - logger.info(s); - } - - @Override - public void info(String s, Object o) { - logger.info(s, o); - } - - @Override - public void info(String s, Object o, Object o1) { - logger.info(s, o, o1); - } - - @Override - public void info(String s, Object... objects) { - logger.info(s, objects); - } - - @Override - public void info(String s, Throwable throwable) { - logger.info(s, throwable); - } - - @Override - public void warn(String s) { - logger.warn(s); - } - - @Override - public void warn(String s, Object o) { - logger.warn(s, o); - } - - @Override - public void warn(String s, Object... objects) { - logger.warn(s, objects); - } - - @Override - public void warn(String s, Object o, Object o1) { - logger.warn(s, o, o1); - } - - @Override - public void warn(String s, Throwable throwable) { - logger.warn(s, throwable); - } - - @Override - public void error(String s) { - logger.error(s); - } - - @Override - public void error(String s, Object o) { - logger.error(s, o); - } - - @Override - public void error(String s, Object o, Object o1) { - logger.error(s, o, o1); - } - - @Override - public void error(String s, Object... objects) { - logger.error(s, objects); - } - - @Override - public void error(String s, Throwable throwable) { - logger.error(s, throwable); - } - } -} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/Appender.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/Appender.java deleted file mode 100755 index c06156310f8..00000000000 --- a/logging/src/main/java/org/apache/rocketmq/logging/inner/Appender.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging.inner; - - -import java.io.InterruptedIOException; -import java.util.Enumeration; -import java.util.Vector; - -public abstract class Appender { - - public static final int CODE_WRITE_FAILURE = 1; - public static final int CODE_FLUSH_FAILURE = 2; - public static final int CODE_CLOSE_FAILURE = 3; - public static final int CODE_FILE_OPEN_FAILURE = 4; - - public final static String LINE_SEP = System.getProperty("line.separator"); - - boolean firstTime = true; - - protected Layout layout; - - protected String name; - - protected boolean closed = false; - - public void activateOptions() { - } - - abstract protected void append(LoggingEvent event); - - public void finalize() { - try { - super.finalize(); - } catch (Throwable throwable) { - SysLogger.error("Finalizing appender named [" + name + "]. error", throwable); - } - if (this.closed) { - return; - } - - SysLogger.debug("Finalizing appender named [" + name + "]."); - close(); - } - - public Layout getLayout() { - return layout; - } - - public final String getName() { - return this.name; - } - - public synchronized void doAppend(LoggingEvent event) { - if (closed) { - SysLogger.error("Attempted to append to closed appender named [" + name + "]."); - return; - } - this.append(event); - } - - public void setLayout(Layout layout) { - this.layout = layout; - } - - public void setName(String name) { - this.name = name; - } - - public abstract void close(); - - public void handleError(String message, Exception e, int errorCode) { - if (e instanceof InterruptedIOException || e instanceof InterruptedException) { - Thread.currentThread().interrupt(); - } - if (firstTime) { - SysLogger.error(message + " code:" + errorCode, e); - firstTime = false; - } - } - - public void handleError(String message) { - if (firstTime) { - SysLogger.error(message); - firstTime = false; - } - } - - - public interface AppenderPipeline { - - void addAppender(Appender newAppender); - - Enumeration getAllAppenders(); - - Appender getAppender(String name); - - boolean isAttached(Appender appender); - - void removeAllAppenders(); - - void removeAppender(Appender appender); - - void removeAppender(String name); - } - - - public static class AppenderPipelineImpl implements AppenderPipeline { - - - protected Vector appenderList; - - public void addAppender(Appender newAppender) { - if (newAppender == null) { - return; - } - - if (appenderList == null) { - appenderList = new Vector(1); - } - if (!appenderList.contains(newAppender)) { - appenderList.addElement(newAppender); - } - } - - public int appendLoopOnAppenders(LoggingEvent event) { - int size = 0; - Appender appender; - - if (appenderList != null) { - size = appenderList.size(); - for (int i = 0; i < size; i++) { - appender = appenderList.elementAt(i); - appender.doAppend(event); - } - } - return size; - } - - public Enumeration getAllAppenders() { - if (appenderList == null) { - return null; - } else { - return appenderList.elements(); - } - } - - public Appender getAppender(String name) { - if (appenderList == null || name == null) { - return null; - } - - int size = appenderList.size(); - Appender appender; - for (int i = 0; i < size; i++) { - appender = appenderList.elementAt(i); - if (name.equals(appender.getName())) { - return appender; - } - } - return null; - } - - public boolean isAttached(Appender appender) { - if (appenderList == null || appender == null) { - return false; - } - - int size = appenderList.size(); - Appender a; - for (int i = 0; i < size; i++) { - a = appenderList.elementAt(i); - if (a == appender) { - return true; - } - } - return false; - } - - public void removeAllAppenders() { - if (appenderList != null) { - int len = appenderList.size(); - for (int i = 0; i < len; i++) { - Appender a = appenderList.elementAt(i); - a.close(); - } - appenderList.removeAllElements(); - appenderList = null; - } - } - - public void removeAppender(Appender appender) { - if (appender == null || appenderList == null) { - return; - } - appenderList.removeElement(appender); - } - - public void removeAppender(String name) { - if (name == null || appenderList == null) { - return; - } - int size = appenderList.size(); - for (int i = 0; i < size; i++) { - if (name.equals((appenderList.elementAt(i)).getName())) { - appenderList.removeElementAt(i); - break; - } - } - } - - } -} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/Level.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/Level.java deleted file mode 100755 index 0dc81d74e67..00000000000 --- a/logging/src/main/java/org/apache/rocketmq/logging/inner/Level.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging.inner; - -import java.io.Serializable; - -public class Level implements Serializable { - - transient int level; - transient String levelStr; - transient int syslogEquivalent; - - public final static int OFF_INT = Integer.MAX_VALUE; - public final static int ERROR_INT = 40000; - public final static int WARN_INT = 30000; - public final static int INFO_INT = 20000; - public final static int DEBUG_INT = 10000; - public final static int ALL_INT = Integer.MIN_VALUE; - - - private static final String ALL_NAME = "ALL"; - - private static final String DEBUG_NAME = "DEBUG"; - - private static final String INFO_NAME = "INFO"; - - private static final String WARN_NAME = "WARN"; - - private static final String ERROR_NAME = "ERROR"; - - private static final String OFF_NAME = "OFF"; - - final static public Level OFF = new Level(OFF_INT, OFF_NAME, 0); - - final static public Level ERROR = new Level(ERROR_INT, ERROR_NAME, 3); - - final static public Level WARN = new Level(WARN_INT, WARN_NAME, 4); - - final static public Level INFO = new Level(INFO_INT, INFO_NAME, 6); - - final static public Level DEBUG = new Level(DEBUG_INT, DEBUG_NAME, 7); - - final static public Level ALL = new Level(ALL_INT, ALL_NAME, 7); - - static final long serialVersionUID = 3491141966387921974L; - - protected Level(int level, String levelStr, int syslogEquivalent) { - this.level = level; - this.levelStr = levelStr; - this.syslogEquivalent = syslogEquivalent; - } - - public static Level toLevel(String sArg) { - return toLevel(sArg, Level.DEBUG); - } - - public static Level toLevel(int val) { - return toLevel(val, Level.DEBUG); - } - - public static Level toLevel(int val, Level defaultLevel) { - switch (val) { - case ALL_INT: - return ALL; - case DEBUG_INT: - return Level.DEBUG; - case INFO_INT: - return Level.INFO; - case WARN_INT: - return Level.WARN; - case ERROR_INT: - return Level.ERROR; - case OFF_INT: - return OFF; - default: - return defaultLevel; - } - } - - public static Level toLevel(String sArg, Level defaultLevel) { - if (sArg == null) { - return defaultLevel; - } - String s = sArg.toUpperCase(); - - if (s.equals(ALL_NAME)) { - return Level.ALL; - } - if (s.equals(DEBUG_NAME)) { - return Level.DEBUG; - } - if (s.equals(INFO_NAME)) { - return Level.INFO; - } - if (s.equals(WARN_NAME)) { - return Level.WARN; - } - if (s.equals(ERROR_NAME)) { - return Level.ERROR; - } - if (s.equals(OFF_NAME)) { - return Level.OFF; - } - if (s.equals(INFO_NAME)) { - return Level.INFO; - } - return defaultLevel; - } - - - public boolean equals(Object o) { - if (o instanceof Level) { - Level r = (Level) o; - return this.level == r.level; - } else { - return false; - } - } - - @Override - public int hashCode() { - int result = level; - result = 31 * result + (levelStr != null ? levelStr.hashCode() : 0); - result = 31 * result + syslogEquivalent; - return result; - } - - public boolean isGreaterOrEqual(Level r) { - return level >= r.level; - } - - final public String toString() { - return levelStr; - } - - public final int toInt() { - return level; - } - -} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/Logger.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/Logger.java deleted file mode 100755 index 470ed41daf9..00000000000 --- a/logging/src/main/java/org/apache/rocketmq/logging/inner/Logger.java +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging.inner; - -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.Vector; - - -public class Logger implements Appender.AppenderPipeline { - - private static final String FQCN = Logger.class.getName(); - - private static final DefaultLoggerRepository REPOSITORY = new DefaultLoggerRepository(new RootLogger(Level.DEBUG)); - - public static LoggerRepository getRepository() { - return REPOSITORY; - } - - private String name; - - volatile private Level level; - - volatile private Logger parent; - - Appender.AppenderPipelineImpl appenderPipeline; - - private boolean additive = true; - - private Logger(String name) { - this.name = name; - } - - static public Logger getLogger(String name) { - return getRepository().getLogger(name); - } - - static public Logger getLogger(Class clazz) { - return getRepository().getLogger(clazz.getName()); - } - - public static Logger getRootLogger() { - return getRepository().getRootLogger(); - } - - synchronized public void addAppender(Appender newAppender) { - if (appenderPipeline == null) { - appenderPipeline = new Appender.AppenderPipelineImpl(); - } - appenderPipeline.addAppender(newAppender); - } - - public void callAppenders(LoggingEvent event) { - int writes = 0; - - for (Logger logger = this; logger != null; logger = logger.parent) { - synchronized (logger) { - if (logger.appenderPipeline != null) { - writes += logger.appenderPipeline.appendLoopOnAppenders(event); - } - if (!logger.additive) { - break; - } - } - } - - if (writes == 0) { - getRepository().emitNoAppenderWarning(this); - } - } - - synchronized void closeNestedAppenders() { - Enumeration enumeration = this.getAllAppenders(); - if (enumeration != null) { - while (enumeration.hasMoreElements()) { - Appender a = (Appender) enumeration.nextElement(); - if (a instanceof Appender.AppenderPipeline) { - a.close(); - } - } - } - } - - public void debug(Object message) { - if (getRepository().isDisabled(Level.DEBUG_INT)) { - return; - } - if (Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) { - forcedLog(FQCN, Level.DEBUG, message, null); - } - } - - - public void debug(Object message, Throwable t) { - if (getRepository().isDisabled(Level.DEBUG_INT)) { - return; - } - if (Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) { - forcedLog(FQCN, Level.DEBUG, message, t); - } - } - - - public void error(Object message) { - if (getRepository().isDisabled(Level.ERROR_INT)) { - return; - } - if (Level.ERROR.isGreaterOrEqual(this.getEffectiveLevel())) { - forcedLog(FQCN, Level.ERROR, message, null); - } - } - - public void error(Object message, Throwable t) { - if (getRepository().isDisabled(Level.ERROR_INT)) { - return; - } - if (Level.ERROR.isGreaterOrEqual(this.getEffectiveLevel())) { - forcedLog(FQCN, Level.ERROR, message, t); - } - - } - - - protected void forcedLog(String fqcn, Level level, Object message, Throwable t) { - callAppenders(new LoggingEvent(fqcn, this, level, message, t)); - } - - - synchronized public Enumeration getAllAppenders() { - if (appenderPipeline == null) { - return null; - } else { - return appenderPipeline.getAllAppenders(); - } - } - - synchronized public Appender getAppender(String name) { - if (appenderPipeline == null || name == null) { - return null; - } - - return appenderPipeline.getAppender(name); - } - - public Level getEffectiveLevel() { - for (Logger c = this; c != null; c = c.parent) { - if (c.level != null) { - return c.level; - } - } - return null; - } - - public final String getName() { - return name; - } - - final public Level getLevel() { - return this.level; - } - - - public void info(Object message) { - if (getRepository().isDisabled(Level.INFO_INT)) { - return; - } - if (Level.INFO.isGreaterOrEqual(this.getEffectiveLevel())) { - forcedLog(FQCN, Level.INFO, message, null); - } - } - - public void info(Object message, Throwable t) { - if (getRepository().isDisabled(Level.INFO_INT)) { - return; - } - if (Level.INFO.isGreaterOrEqual(this.getEffectiveLevel())) { - forcedLog(FQCN, Level.INFO, message, t); - } - } - - public boolean isAttached(Appender appender) { - return appender != null && appenderPipeline != null && appenderPipeline.isAttached(appender); - } - - synchronized public void removeAllAppenders() { - if (appenderPipeline != null) { - appenderPipeline.removeAllAppenders(); - appenderPipeline = null; - } - } - - synchronized public void removeAppender(Appender appender) { - if (appender == null || appenderPipeline == null) { - return; - } - appenderPipeline.removeAppender(appender); - } - - synchronized public void removeAppender(String name) { - if (name == null || appenderPipeline == null) { - return; - } - appenderPipeline.removeAppender(name); - } - - public void setAdditivity(boolean additive) { - this.additive = additive; - } - - public void setLevel(Level level) { - this.level = level; - } - - public void warn(Object message) { - if (getRepository().isDisabled(Level.WARN_INT)) { - return; - } - - if (Level.WARN.isGreaterOrEqual(this.getEffectiveLevel())) { - forcedLog(FQCN, Level.WARN, message, null); - } - } - - public void warn(Object message, Throwable t) { - if (getRepository().isDisabled(Level.WARN_INT)) { - return; - } - if (Level.WARN.isGreaterOrEqual(this.getEffectiveLevel())) { - forcedLog(FQCN, Level.WARN, message, t); - } - } - - public interface LoggerRepository { - - boolean isDisabled(int level); - - void setLogLevel(Level level); - - void emitNoAppenderWarning(Logger cat); - - Level getLogLevel(); - - Logger getLogger(String name); - - Logger getRootLogger(); - - Logger exists(String name); - - void shutdown(); - - Enumeration getCurrentLoggers(); - } - - public static class ProvisionNode extends Vector { - - ProvisionNode(Logger logger) { - super(); - addElement(logger); - } - } - - public static class DefaultLoggerRepository implements LoggerRepository { - - final Hashtable ht = new Hashtable(); - Logger root; - - int logLevelInt; - Level logLevel; - - boolean emittedNoAppenderWarning = false; - - public DefaultLoggerRepository(Logger root) { - this.root = root; - setLogLevel(Level.ALL); - } - - public void emitNoAppenderWarning(Logger cat) { - if (!this.emittedNoAppenderWarning) { - SysLogger.warn("No appenders could be found for logger (" + cat.getName() + ")."); - SysLogger.warn("Please initialize the logger system properly."); - this.emittedNoAppenderWarning = true; - } - } - - public Logger exists(String name) { - Object o = ht.get(new CategoryKey(name)); - if (o instanceof Logger) { - return (Logger) o; - } else { - return null; - } - } - - public void setLogLevel(Level l) { - if (l != null) { - logLevelInt = l.level; - logLevel = l; - } - } - - public Level getLogLevel() { - return logLevel; - } - - - public Logger getLogger(String name) { - CategoryKey key = new CategoryKey(name); - Logger logger; - - synchronized (ht) { - Object o = ht.get(key); - if (o == null) { - logger = makeNewLoggerInstance(name); - ht.put(key, logger); - updateParents(logger); - return logger; - } else if (o instanceof Logger) { - return (Logger) o; - } else if (o instanceof ProvisionNode) { - logger = makeNewLoggerInstance(name); - ht.put(key, logger); - updateChildren((ProvisionNode) o, logger); - updateParents(logger); - return logger; - } else { - return null; - } - } - } - - public Logger makeNewLoggerInstance(String name) { - return new Logger(name); - } - - public Enumeration getCurrentLoggers() { - Vector loggers = new Vector(ht.size()); - - Enumeration elems = ht.elements(); - while (elems.hasMoreElements()) { - Object o = elems.nextElement(); - if (o instanceof Logger) { - Logger logger = (Logger)o; - loggers.addElement(logger); - } - } - return loggers.elements(); - } - - - public Logger getRootLogger() { - return root; - } - - public boolean isDisabled(int level) { - return logLevelInt > level; - } - - - public void shutdown() { - Logger root = getRootLogger(); - root.closeNestedAppenders(); - - synchronized (ht) { - Enumeration cats = this.getCurrentLoggers(); - while (cats.hasMoreElements()) { - Logger c = (Logger) cats.nextElement(); - c.closeNestedAppenders(); - } - root.removeAllAppenders(); - } - } - - - private void updateParents(Logger cat) { - String name = cat.name; - int length = name.length(); - boolean parentFound = false; - - for (int i = name.lastIndexOf('.', length - 1); i >= 0; - i = name.lastIndexOf('.', i - 1)) { - String substr = name.substring(0, i); - - CategoryKey key = new CategoryKey(substr); - Object o = ht.get(key); - if (o == null) { - ht.put(key, new ProvisionNode(cat)); - } else if (o instanceof Logger) { - parentFound = true; - cat.parent = (Logger) o; - break; - } else if (o instanceof ProvisionNode) { - ((ProvisionNode) o).addElement(cat); - } else { - Exception e = new IllegalStateException("unexpected object type " + o.getClass() + " in ht."); - e.printStackTrace(); - } - } - if (!parentFound) { - cat.parent = root; - } - } - - private void updateChildren(ProvisionNode pn, Logger logger) { - final int last = pn.size(); - - for (int i = 0; i < last; i++) { - Logger l = pn.elementAt(i); - if (!l.parent.name.startsWith(logger.name)) { - logger.parent = l.parent; - l.parent = logger; - } - } - } - - private class CategoryKey { - - String name; - int hashCache; - - CategoryKey(String name) { - this.name = name; - hashCache = name.hashCode(); - } - - final public int hashCode() { - return hashCache; - } - - final public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o != null && o instanceof CategoryKey) { - CategoryKey cc = (CategoryKey) o; - return name.equals(cc.name); - } else { - return false; - } - } - } - - } - - public static class RootLogger extends Logger { - - public RootLogger(Level level) { - super("root"); - setLevel(level); - } - } -} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingBuilder.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingBuilder.java deleted file mode 100644 index 7468cd498f5..00000000000 --- a/logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingBuilder.java +++ /dev/null @@ -1,1230 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging.inner; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FilterWriter; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.text.MessageFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.Enumeration; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; - -public class LoggingBuilder { - - public static final String SYSTEM_OUT = "System.out"; - public static final String SYSTEM_ERR = "System.err"; - - public static final String LOGGING_ENCODING = "rocketmq.logging.inner.encoding"; - public static final String ENCODING = System.getProperty(LOGGING_ENCODING, "UTF-8"); - - public static AppenderBuilder newAppenderBuilder() { - return new AppenderBuilder(); - } - - public static class AppenderBuilder { - private AsyncAppender asyncAppender; - - private Appender appender = null; - - private AppenderBuilder() { - - } - - public AppenderBuilder withLayout(Layout layout) { - appender.setLayout(layout); - return this; - } - - public AppenderBuilder withName(String name) { - appender.setName(name); - return this; - } - - public AppenderBuilder withConsoleAppender(String target) { - ConsoleAppender consoleAppender = new ConsoleAppender(); - consoleAppender.setTarget(target); - consoleAppender.activateOptions(); - this.appender = consoleAppender; - return this; - } - - public AppenderBuilder withFileAppender(String file) { - FileAppender appender = new FileAppender(); - appender.setFile(file); - appender.setAppend(true); - appender.setBufferedIO(false); - appender.setEncoding(ENCODING); - appender.setImmediateFlush(true); - appender.activateOptions(); - this.appender = appender; - return this; - } - - public AppenderBuilder withRollingFileAppender(String file, String maxFileSize, int maxFileIndex) { - RollingFileAppender appender = new RollingFileAppender(); - appender.setFile(file); - appender.setAppend(true); - appender.setBufferedIO(false); - appender.setEncoding(ENCODING); - appender.setImmediateFlush(true); - appender.setMaximumFileSize(Integer.parseInt(maxFileSize)); - appender.setMaxBackupIndex(maxFileIndex); - appender.activateOptions(); - this.appender = appender; - return this; - } - - public AppenderBuilder withDailyFileRollingAppender(String file, String datePattern) { - DailyRollingFileAppender appender = new DailyRollingFileAppender(); - appender.setFile(file); - appender.setAppend(true); - appender.setBufferedIO(false); - appender.setEncoding(ENCODING); - appender.setImmediateFlush(true); - appender.setDatePattern(datePattern); - appender.activateOptions(); - this.appender = appender; - return this; - } - - public AppenderBuilder withAsync(boolean blocking, int buffSize) { - AsyncAppender asyncAppender = new AsyncAppender(); - asyncAppender.setBlocking(blocking); - asyncAppender.setBufferSize(buffSize); - this.asyncAppender = asyncAppender; - return this; - } - - public Appender build() { - if (appender == null) { - throw new RuntimeException("please specify appender first"); - } - if (asyncAppender != null) { - asyncAppender.addAppender(appender); - return asyncAppender; - } else { - return appender; - } - } - } - - public static class AsyncAppender extends Appender implements Appender.AppenderPipeline { - - public static final int DEFAULT_BUFFER_SIZE = 128; - - private final List buffer = new ArrayList(); - - private final Map discardMap = new HashMap(); - - private int bufferSize = DEFAULT_BUFFER_SIZE; - - private final AppenderPipelineImpl appenderPipeline; - - private final Thread dispatcher; - - private boolean blocking = true; - - public AsyncAppender() { - appenderPipeline = new AppenderPipelineImpl(); - - dispatcher = new Thread(new Dispatcher(this, buffer, discardMap, appenderPipeline)); - - dispatcher.setDaemon(true); - - dispatcher.setName("AsyncAppender-Dispatcher-" + dispatcher.getName()); - dispatcher.start(); - } - - public void addAppender(final Appender newAppender) { - synchronized (appenderPipeline) { - appenderPipeline.addAppender(newAppender); - } - } - - public void append(final LoggingEvent event) { - if ((dispatcher == null) || !dispatcher.isAlive() || (bufferSize <= 0)) { - synchronized (appenderPipeline) { - appenderPipeline.appendLoopOnAppenders(event); - } - - return; - } - - event.getThreadName(); - event.getRenderedMessage(); - - synchronized (buffer) { - while (true) { - int previousSize = buffer.size(); - - if (previousSize < bufferSize) { - buffer.add(event); - - if (previousSize == 0) { - buffer.notifyAll(); - } - - break; - } - - boolean discard = true; - if (blocking - && !Thread.interrupted() - && Thread.currentThread() != dispatcher) { - try { - buffer.wait(); - discard = false; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - if (discard) { - String loggerName = event.getLoggerName(); - DiscardSummary summary = discardMap.get(loggerName); - - if (summary == null) { - summary = new DiscardSummary(event); - discardMap.put(loggerName, summary); - } else { - summary.add(event); - } - - break; - } - } - } - } - - public void close() { - - synchronized (buffer) { - closed = true; - buffer.notifyAll(); - } - - try { - dispatcher.join(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - SysLogger.error( - "Got an InterruptedException while waiting for the " - + "dispatcher to finish.", e); - } - - synchronized (appenderPipeline) { - Enumeration iter = appenderPipeline.getAllAppenders(); - if (iter != null) { - while (iter.hasMoreElements()) { - Object next = iter.nextElement(); - if (next instanceof Appender) { - ((Appender) next).close(); - } - } - } - } - } - - public Enumeration getAllAppenders() { - synchronized (appenderPipeline) { - return appenderPipeline.getAllAppenders(); - } - } - - public Appender getAppender(final String name) { - synchronized (appenderPipeline) { - return appenderPipeline.getAppender(name); - } - } - - public boolean isAttached(final Appender appender) { - synchronized (appenderPipeline) { - return appenderPipeline.isAttached(appender); - } - } - - public void removeAllAppenders() { - synchronized (appenderPipeline) { - appenderPipeline.removeAllAppenders(); - } - } - - public void removeAppender(final Appender appender) { - synchronized (appenderPipeline) { - appenderPipeline.removeAppender(appender); - } - } - - public void removeAppender(final String name) { - synchronized (appenderPipeline) { - appenderPipeline.removeAppender(name); - } - } - - public void setBufferSize(final int size) { - if (size < 0) { - throw new NegativeArraySizeException("size"); - } - - synchronized (buffer) { - bufferSize = (size < 1) ? 1 : size; - buffer.notifyAll(); - } - } - - public int getBufferSize() { - return bufferSize; - } - - public void setBlocking(final boolean value) { - synchronized (buffer) { - blocking = value; - buffer.notifyAll(); - } - } - - public boolean getBlocking() { - return blocking; - } - - private final class DiscardSummary { - - private LoggingEvent maxEvent; - - private int count; - - public DiscardSummary(final LoggingEvent event) { - maxEvent = event; - count = 1; - } - - public void add(final LoggingEvent event) { - if (event.getLevel().toInt() > maxEvent.getLevel().toInt()) { - maxEvent = event; - } - count++; - } - - public LoggingEvent createEvent() { - String msg = - MessageFormat.format( - "Discarded {0} messages due to full event buffer including: {1}", - count, maxEvent.getMessage()); - - return new LoggingEvent( - "AsyncAppender.DONT_REPORT_LOCATION", - Logger.getLogger(maxEvent.getLoggerName()), - maxEvent.getLevel(), - msg, - null); - } - } - - private class Dispatcher implements Runnable { - - private final AsyncAppender parent; - - private final List buffer; - - private final Map discardMap; - - private final AppenderPipelineImpl appenderPipeline; - - public Dispatcher( - final AsyncAppender parent, final List buffer, final Map discardMap, - final AppenderPipelineImpl appenderPipeline) { - - this.parent = parent; - this.buffer = buffer; - this.appenderPipeline = appenderPipeline; - this.discardMap = discardMap; - } - - public void run() { - boolean isActive = true; - - try { - while (isActive) { - LoggingEvent[] events = null; - - synchronized (buffer) { - int bufferSize = buffer.size(); - isActive = !parent.closed; - - while ((bufferSize == 0) && isActive) { - buffer.wait(); - bufferSize = buffer.size(); - isActive = !parent.closed; - } - - if (bufferSize > 0) { - events = new LoggingEvent[bufferSize + discardMap.size()]; - buffer.toArray(events); - - int index = bufferSize; - Collection values = discardMap.values(); - for (DiscardSummary value : values) { - events[index++] = value.createEvent(); - } - - buffer.clear(); - discardMap.clear(); - - buffer.notifyAll(); - } - } - if (events != null) { - for (LoggingEvent event : events) { - synchronized (appenderPipeline) { - appenderPipeline.appendLoopOnAppenders(event); - } - } - } - } - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } - } - - private static class QuietWriter extends FilterWriter { - - protected Appender appender; - - public QuietWriter(Writer writer, Appender appender) { - super(writer); - this.appender = appender; - } - - public void write(String string) { - if (string != null) { - try { - out.write(string); - } catch (Exception e) { - appender.handleError("Failed to write [" + string + "].", e, - Appender.CODE_WRITE_FAILURE); - } - } - } - - public void flush() { - try { - out.flush(); - } catch (Exception e) { - appender.handleError("Failed to flush writer,", e, - Appender.CODE_FLUSH_FAILURE); - } - } - } - - public static class WriterAppender extends Appender { - - - protected boolean immediateFlush = true; - - protected String encoding; - - - protected QuietWriter qw; - - public WriterAppender() { - - } - - public void setImmediateFlush(boolean value) { - immediateFlush = value; - } - - - public boolean getImmediateFlush() { - return immediateFlush; - } - - public void activateOptions() { - } - - - public void append(LoggingEvent event) { - if (!checkEntryConditions()) { - return; - } - subAppend(event); - } - - protected boolean checkEntryConditions() { - if (this.closed) { - SysLogger.warn("Not allowed to write to a closed appender."); - return false; - } - - if (this.qw == null) { - handleError("No output stream or file set for the appender named [" + - name + "]."); - return false; - } - - if (this.layout == null) { - handleError("No layout set for the appender named [" + name + "]."); - return false; - } - return true; - } - - public synchronized void close() { - if (this.closed) { - return; - } - this.closed = true; - writeFooter(); - reset(); - } - - protected void closeWriter() { - if (qw != null) { - try { - qw.close(); - } catch (IOException e) { - handleError("Could not close " + qw, e, CODE_CLOSE_FAILURE); - } - } - } - - protected OutputStreamWriter createWriter(OutputStream os) { - OutputStreamWriter retval = null; - - String enc = getEncoding(); - if (enc != null) { - try { - retval = new OutputStreamWriter(os, enc); - } catch (IOException e) { - SysLogger.warn("Error initializing output writer."); - SysLogger.warn("Unsupported encoding?"); - } - } - if (retval == null) { - retval = new OutputStreamWriter(os); - } - return retval; - } - - public String getEncoding() { - return encoding; - } - - public void setEncoding(String value) { - encoding = value; - } - - - public synchronized void setWriter(Writer writer) { - reset(); - this.qw = new QuietWriter(writer, this); - writeHeader(); - } - - protected void subAppend(LoggingEvent event) { - this.qw.write(this.layout.format(event)); - - if (layout.ignoresThrowable()) { - String[] s = event.getThrowableStr(); - if (s != null) { - for (String s1 : s) { - this.qw.write(s1); - this.qw.write(LINE_SEP); - } - } - } - - if (shouldFlush(event)) { - this.qw.flush(); - } - } - - protected void reset() { - closeWriter(); - this.qw = null; - } - - protected void writeFooter() { - if (layout != null) { - String f = layout.getFooter(); - if (f != null && this.qw != null) { - this.qw.write(f); - this.qw.flush(); - } - } - } - - protected void writeHeader() { - if (layout != null) { - String h = layout.getHeader(); - if (h != null && this.qw != null) { - this.qw.write(h); - } - } - } - - protected boolean shouldFlush(final LoggingEvent event) { - return event != null && immediateFlush; - } - } - - - public static class FileAppender extends WriterAppender { - - protected boolean fileAppend = true; - - protected String fileName = null; - - protected boolean bufferedIO = false; - - protected int bufferSize = 8 * 1024; - - public FileAppender() { - } - - public FileAppender(Layout layout, String filename, boolean append) - throws IOException { - this.layout = layout; - this.setFile(filename, append, false, bufferSize); - } - - public void setFile(String file) { - fileName = file.trim(); - } - - public boolean getAppend() { - return fileAppend; - } - - public String getFile() { - return fileName; - } - - public void activateOptions() { - if (fileName != null) { - try { - setFile(fileName, fileAppend, bufferedIO, bufferSize); - } catch (IOException e) { - handleError("setFile(" + fileName + "," + fileAppend + ") call failed.", - e, CODE_FILE_OPEN_FAILURE); - } - } else { - SysLogger.warn("File option not set for appender [" + name + "]."); - SysLogger.warn("Are you using FileAppender instead of ConsoleAppender?"); - } - } - - protected void closeFile() { - if (this.qw != null) { - try { - this.qw.close(); - } catch (IOException e) { - if (e instanceof InterruptedIOException) { - Thread.currentThread().interrupt(); - } - SysLogger.error("Could not close " + qw, e); - } - } - } - - public boolean getBufferedIO() { - return this.bufferedIO; - } - - public int getBufferSize() { - return this.bufferSize; - } - - public void setAppend(boolean flag) { - fileAppend = flag; - } - - public void setBufferedIO(boolean bufferedIO) { - this.bufferedIO = bufferedIO; - if (bufferedIO) { - immediateFlush = false; - } - } - - public void setBufferSize(int bufferSize) { - this.bufferSize = bufferSize; - } - - public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize) - throws IOException { - SysLogger.debug("setFile called: " + fileName + ", " + append); - - if (bufferedIO) { - setImmediateFlush(false); - } - - reset(); - FileOutputStream ostream; - try { - ostream = new FileOutputStream(fileName, append); - } catch (FileNotFoundException ex) { - String parentName = new File(fileName).getParent(); - if (parentName != null) { - File parentDir = new File(parentName); - if (!parentDir.exists() && parentDir.mkdirs()) { - ostream = new FileOutputStream(fileName, append); - } else { - throw ex; - } - } else { - throw ex; - } - } - Writer fw = createWriter(ostream); - if (bufferedIO) { - fw = new BufferedWriter(fw, bufferSize); - } - this.setQWForFiles(fw); - this.fileName = fileName; - this.fileAppend = append; - this.bufferedIO = bufferedIO; - this.bufferSize = bufferSize; - writeHeader(); - SysLogger.debug("setFile ended"); - } - - protected void setQWForFiles(Writer writer) { - this.qw = new QuietWriter(writer, this); - } - - protected void reset() { - closeFile(); - this.fileName = null; - super.reset(); - } - } - - - public static class RollingFileAppender extends FileAppender { - - protected long maxFileSize = 10 * 1024 * 1024; - - protected int maxBackupIndex = 1; - - private long nextRollover = 0; - - public RollingFileAppender() { - super(); - } - - public int getMaxBackupIndex() { - return maxBackupIndex; - } - - public long getMaximumFileSize() { - return maxFileSize; - } - - public void rollOver() { - File target; - File file; - - if (qw != null) { - long size = ((CountingQuietWriter) qw).getCount(); - SysLogger.debug("rolling over count=" + size); - nextRollover = size + maxFileSize; - } - SysLogger.debug("maxBackupIndex=" + maxBackupIndex); - - boolean renameSucceeded = true; - if (maxBackupIndex > 0) { - file = new File(fileName + '.' + maxBackupIndex); - if (file.exists()) { - renameSucceeded = file.delete(); - } - - for (int i = maxBackupIndex - 1; i >= 1 && renameSucceeded; i--) { - file = new File(fileName + "." + i); - if (file.exists()) { - target = new File(fileName + '.' + (i + 1)); - SysLogger.debug("Renaming file " + file + " to " + target); - renameSucceeded = file.renameTo(target); - } - } - - if (renameSucceeded) { - target = new File(fileName + "." + 1); - - this.closeFile(); // keep windows happy. - - file = new File(fileName); - SysLogger.debug("Renaming file " + file + " to " + target); - renameSucceeded = file.renameTo(target); - - if (!renameSucceeded) { - try { - this.setFile(fileName, true, bufferedIO, bufferSize); - } catch (IOException e) { - if (e instanceof InterruptedIOException) { - Thread.currentThread().interrupt(); - } - SysLogger.error("setFile(" + fileName + ", true) call failed.", e); - } - } - } - } - - if (renameSucceeded) { - try { - this.setFile(fileName, false, bufferedIO, bufferSize); - nextRollover = 0; - } catch (IOException e) { - if (e instanceof InterruptedIOException) { - Thread.currentThread().interrupt(); - } - SysLogger.error("setFile(" + fileName + ", false) call failed.", e); - } - } - } - - public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize) - throws IOException { - super.setFile(fileName, append, this.bufferedIO, this.bufferSize); - if (append) { - File f = new File(fileName); - ((CountingQuietWriter) qw).setCount(f.length()); - } - } - - public void setMaxBackupIndex(int maxBackups) { - this.maxBackupIndex = maxBackups; - } - - public void setMaximumFileSize(long maxFileSize) { - this.maxFileSize = maxFileSize; - } - - protected void setQWForFiles(Writer writer) { - this.qw = new CountingQuietWriter(writer, this); - } - - protected void subAppend(LoggingEvent event) { - super.subAppend(event); - if (fileName != null && qw != null) { - long size = ((CountingQuietWriter) qw).getCount(); - if (size >= maxFileSize && size >= nextRollover) { - rollOver(); - } - } - } - - protected class CountingQuietWriter extends QuietWriter { - - protected long count; - - public CountingQuietWriter(Writer writer, Appender appender) { - super(writer, appender); - } - - public void write(String string) { - try { - out.write(string); - count += string.length(); - } catch (IOException e) { - appender.handleError("Write failure.", e, Appender.CODE_WRITE_FAILURE); - } - } - - public long getCount() { - return count; - } - - public void setCount(long count) { - this.count = count; - } - - } - } - - - public static class DailyRollingFileAppender extends FileAppender { - - static final int TOP_OF_TROUBLE = -1; - static final int TOP_OF_MINUTE = 0; - static final int TOP_OF_HOUR = 1; - static final int HALF_DAY = 2; - static final int TOP_OF_DAY = 3; - static final int TOP_OF_WEEK = 4; - static final int TOP_OF_MONTH = 5; - - - /** - * The date pattern. By default, the pattern is set to - * "'.'yyyy-MM-dd" meaning daily rollover. - */ - private String datePattern = "'.'yyyy-MM-dd"; - - private String scheduledFilename; - - private long nextCheck = System.currentTimeMillis() - 1; - - Date now = new Date(); - - SimpleDateFormat sdf; - - RollingCalendar rc = new RollingCalendar(); - - final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT"); - - - public void setDatePattern(String pattern) { - datePattern = pattern; - } - - public String getDatePattern() { - return datePattern; - } - - public void activateOptions() { - super.activateOptions(); - if (datePattern != null && fileName != null) { - now.setTime(System.currentTimeMillis()); - sdf = new SimpleDateFormat(datePattern); - int type = computeCheckPeriod(); - printPeriodicity(type); - rc.setType(type); - File file = new File(fileName); - scheduledFilename = fileName + sdf.format(new Date(file.lastModified())); - - } else { - SysLogger.error("Either File or DatePattern options are not set for appender [" + name + "]."); - } - } - - void printPeriodicity(int type) { - switch (type) { - case TOP_OF_MINUTE: - SysLogger.debug("Appender [" + name + "] to be rolled every minute."); - break; - case TOP_OF_HOUR: - SysLogger.debug("Appender [" + name + "] to be rolled on top of every hour."); - break; - case HALF_DAY: - SysLogger.debug("Appender [" + name + "] to be rolled at midday and midnight."); - break; - case TOP_OF_DAY: - SysLogger.debug("Appender [" + name + "] to be rolled at midnight."); - break; - case TOP_OF_WEEK: - SysLogger.debug("Appender [" + name + "] to be rolled at start of week."); - break; - case TOP_OF_MONTH: - SysLogger.debug("Appender [" + name + "] to be rolled at start of every month."); - break; - default: - SysLogger.warn("Unknown periodicity for appender [" + name + "]."); - } - } - - int computeCheckPeriod() { - RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.getDefault()); - // set sate to 1970-01-01 00:00:00 GMT - Date epoch = new Date(0); - if (datePattern != null) { - for (int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) { - SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern); - simpleDateFormat.setTimeZone(gmtTimeZone); - String r0 = simpleDateFormat.format(epoch); - rollingCalendar.setType(i); - Date next = new Date(rollingCalendar.getNextCheckMillis(epoch)); - String r1 = simpleDateFormat.format(next); - if (r0 != null && r1 != null && !r0.equals(r1)) { - return i; - } - } - } - return TOP_OF_TROUBLE; - } - - void rollOver() throws IOException { - - if (datePattern == null) { - handleError("Missing DatePattern option in rollOver()."); - return; - } - - String datedFilename = fileName + sdf.format(now); - - if (scheduledFilename.equals(datedFilename)) { - return; - } - this.closeFile(); - - File target = new File(scheduledFilename); - if (target.exists() && !target.delete()) { - SysLogger.error("Failed to delete [" + scheduledFilename + "]."); - } - - File file = new File(fileName); - boolean result = file.renameTo(target); - if (result) { - SysLogger.debug(fileName + " -> " + scheduledFilename); - } else { - SysLogger.error("Failed to rename [" + fileName + "] to [" + scheduledFilename + "]."); - } - - try { - this.setFile(fileName, true, this.bufferedIO, this.bufferSize); - } catch (IOException e) { - handleError("setFile(" + fileName + ", true) call failed."); - } - scheduledFilename = datedFilename; - } - - protected void subAppend(LoggingEvent event) { - long n = System.currentTimeMillis(); - if (n >= nextCheck) { - now.setTime(n); - nextCheck = rc.getNextCheckMillis(now); - try { - rollOver(); - } catch (IOException ioe) { - if (ioe instanceof InterruptedIOException) { - Thread.currentThread().interrupt(); - } - SysLogger.error("rollOver() failed.", ioe); - } - } - super.subAppend(event); - } - } - - private static class RollingCalendar extends GregorianCalendar { - private static final long serialVersionUID = -3560331770601814177L; - - int type = DailyRollingFileAppender.TOP_OF_TROUBLE; - - RollingCalendar() { - super(); - } - - RollingCalendar(TimeZone tz, Locale locale) { - super(tz, locale); - } - - void setType(int type) { - this.type = type; - } - - public long getNextCheckMillis(Date now) { - return getNextCheckDate(now).getTime(); - } - - public Date getNextCheckDate(Date now) { - this.setTime(now); - - switch (type) { - case DailyRollingFileAppender.TOP_OF_MINUTE: - this.set(Calendar.SECOND, 0); - this.set(Calendar.MILLISECOND, 0); - this.add(Calendar.MINUTE, 1); - break; - case DailyRollingFileAppender.TOP_OF_HOUR: - this.set(Calendar.MINUTE, 0); - this.set(Calendar.SECOND, 0); - this.set(Calendar.MILLISECOND, 0); - this.add(Calendar.HOUR_OF_DAY, 1); - break; - case DailyRollingFileAppender.HALF_DAY: - this.set(Calendar.MINUTE, 0); - this.set(Calendar.SECOND, 0); - this.set(Calendar.MILLISECOND, 0); - int hour = get(Calendar.HOUR_OF_DAY); - if (hour < 12) { - this.set(Calendar.HOUR_OF_DAY, 12); - } else { - this.set(Calendar.HOUR_OF_DAY, 0); - this.add(Calendar.DAY_OF_MONTH, 1); - } - break; - case DailyRollingFileAppender.TOP_OF_DAY: - this.set(Calendar.HOUR_OF_DAY, 0); - this.set(Calendar.MINUTE, 0); - this.set(Calendar.SECOND, 0); - this.set(Calendar.MILLISECOND, 0); - this.add(Calendar.DATE, 1); - break; - case DailyRollingFileAppender.TOP_OF_WEEK: - this.set(Calendar.DAY_OF_WEEK, getFirstDayOfWeek()); - this.set(Calendar.HOUR_OF_DAY, 0); - this.set(Calendar.MINUTE, 0); - this.set(Calendar.SECOND, 0); - this.set(Calendar.MILLISECOND, 0); - this.add(Calendar.WEEK_OF_YEAR, 1); - break; - case DailyRollingFileAppender.TOP_OF_MONTH: - this.set(Calendar.DATE, 1); - this.set(Calendar.HOUR_OF_DAY, 0); - this.set(Calendar.MINUTE, 0); - this.set(Calendar.SECOND, 0); - this.set(Calendar.MILLISECOND, 0); - this.add(Calendar.MONTH, 1); - break; - default: - throw new IllegalStateException("Unknown periodicity type."); - } - return getTime(); - } - } - - public static class ConsoleAppender extends WriterAppender { - - protected String target = SYSTEM_OUT; - - public ConsoleAppender() { - } - - public void setTarget(String value) { - String v = value.trim(); - - if (SYSTEM_OUT.equalsIgnoreCase(v)) { - target = SYSTEM_OUT; - } else if (SYSTEM_ERR.equalsIgnoreCase(v)) { - target = SYSTEM_ERR; - } else { - targetWarn(value); - } - } - - public String getTarget() { - return target; - } - - void targetWarn(String val) { - SysLogger.warn("[" + val + "] should be System.out or System.err."); - SysLogger.warn("Using previously set target, System.out by default."); - } - - public void activateOptions() { - if (target.equals(SYSTEM_ERR)) { - setWriter(createWriter(System.err)); - } else { - setWriter(createWriter(System.out)); - } - super.activateOptions(); - } - - protected final void closeWriter() { - - } - } - - public static LayoutBuilder newLayoutBuilder() { - return new LayoutBuilder(); - } - - public static class LayoutBuilder { - - private Layout layout; - - public LayoutBuilder withSimpleLayout() { - layout = new SimpleLayout(); - return this; - } - - public LayoutBuilder withDefaultLayout() { - layout = new DefaultLayout(); - return this; - } - - public Layout build() { - if (layout == null) { - layout = new SimpleLayout(); - } - return layout; - } - } - - public static class SimpleLayout extends Layout { - - @Override - public String format(LoggingEvent event) { - - StringBuilder sb = new StringBuilder(); - sb.append(event.getLevel().toString()); - sb.append(" - "); - sb.append(event.getRenderedMessage()); - sb.append("\r\n"); - return sb.toString(); - } - - @Override - public boolean ignoresThrowable() { - return false; - } - } - - - /** - * %d{yyy-MM-dd HH:mm:ss,SSS} %p %c{1}%L - %m%n - */ - public static class DefaultLayout extends Layout { - @Override - public String format(LoggingEvent event) { - - StringBuilder sb = new StringBuilder(); - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS"); - String format = simpleDateFormat.format(new Date(event.timeStamp)); - sb.append(format); - sb.append(" "); - sb.append(event.getLevel()); - sb.append(" "); - sb.append(event.getLoggerName()); - sb.append(" - "); - sb.append(event.getRenderedMessage()); - String[] throwableStr = event.getThrowableStr(); - if (throwableStr != null) { - sb.append("\r\n"); - for (String s : throwableStr) { - sb.append(s); - sb.append("\r\n"); - } - } - sb.append("\r\n"); - return sb.toString(); - } - - @Override - public boolean ignoresThrowable() { - return false; - } - } -} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingEvent.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingEvent.java deleted file mode 100644 index 44554e2b7e9..00000000000 --- a/logging/src/main/java/org/apache/rocketmq/logging/inner/LoggingEvent.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging.inner; - -import java.io.IOException; -import java.io.InterruptedIOException; -import java.io.LineNumberReader; -import java.io.PrintWriter; -import java.io.StringReader; -import java.io.StringWriter; -import java.util.ArrayList; - -public class LoggingEvent implements java.io.Serializable { - - transient public final String fqnOfCategoryClass; - - transient private Object message; - - transient private Level level; - - transient private Logger logger; - - private String renderedMessage; - - private String threadName; - - public final long timeStamp; - - private Throwable throwable; - - public LoggingEvent(String fqnOfCategoryClass, Logger logger, - Level level, Object message, Throwable throwable) { - this.fqnOfCategoryClass = fqnOfCategoryClass; - this.message = message; - this.logger = logger; - this.throwable = throwable; - this.level = level; - timeStamp = System.currentTimeMillis(); - } - - public Object getMessage() { - if (message != null) { - return message; - } else { - return getRenderedMessage(); - } - } - - public String getRenderedMessage() { - if (renderedMessage == null && message != null) { - if (message instanceof String) { - renderedMessage = (String) message; - } else { - renderedMessage = message.toString(); - } - if (renderedMessage != null) { - renderedMessage = renderedMessage.replace('\r', ' ').replace('\n', ' '); - } - } - return renderedMessage; - } - - public String getThreadName() { - if (threadName == null) { - threadName = (Thread.currentThread()).getName(); - } - return threadName; - } - - public Level getLevel() { - return level; - } - - public String getLoggerName() { - return logger.getName(); - } - - public String[] getThrowableStr() { - if (throwable == null) { - return null; - } - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - try { - throwable.printStackTrace(pw); - } catch (RuntimeException ex) { - SysLogger.warn("InnerLogger print stack trace error", ex); - } - pw.flush(); - LineNumberReader reader = new LineNumberReader( - new StringReader(sw.toString())); - ArrayList lines = new ArrayList(); - try { - String line = reader.readLine(); - while (line != null) { - lines.add(line); - line = reader.readLine(); - } - } catch (IOException ex) { - if (ex instanceof InterruptedIOException) { - Thread.currentThread().interrupt(); - } - lines.add(ex.toString()); - } - String[] tempRep = new String[lines.size()]; - lines.toArray(tempRep); - return tempRep; - } -} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/SysLogger.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/SysLogger.java deleted file mode 100755 index aaba4d6e89d..00000000000 --- a/logging/src/main/java/org/apache/rocketmq/logging/inner/SysLogger.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging.inner; - -public class SysLogger { - - protected static boolean debugEnabled = false; - - private static boolean quietMode = false; - - private static final String PREFIX = "RocketMQLog: "; - private static final String ERR_PREFIX = "RocketMQLog:ERROR "; - private static final String WARN_PREFIX = "RocketMQLog:WARN "; - - public static void setInternalDebugging(boolean enabled) { - debugEnabled = enabled; - } - - public static void debug(String msg) { - if (debugEnabled && !quietMode) { - System.err.println(PREFIX + msg); - } - } - - public static void debug(String msg, Throwable t) { - if (debugEnabled && !quietMode) { - System.err.println(PREFIX + msg); - if (t != null) { - t.printStackTrace(System.out); - } - } - } - - public static void error(String msg) { - if (quietMode) { - return; - } - System.err.println(ERR_PREFIX + msg); - } - - public static void error(String msg, Throwable t) { - if (quietMode) { - return; - } - - System.err.println(ERR_PREFIX + msg); - if (t != null) { - t.printStackTrace(); - } - } - - public static void setQuietMode(boolean quietMode) { - SysLogger.quietMode = quietMode; - } - - public static void warn(String msg) { - if (quietMode) { - return; - } - - System.err.println(WARN_PREFIX + msg); - } - - public static void warn(String msg, Throwable t) { - if (quietMode) { - return; - } - - System.err.println(WARN_PREFIX + msg); - if (t != null) { - t.printStackTrace(); - } - } -} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/BasicLoggerTest.java b/logging/src/test/java/org/apache/rocketmq/logging/BasicLoggerTest.java deleted file mode 100644 index c198704de26..00000000000 --- a/logging/src/test/java/org/apache/rocketmq/logging/BasicLoggerTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import org.apache.rocketmq.logging.inner.Level; -import org.apache.rocketmq.logging.inner.Logger; -import org.apache.rocketmq.logging.inner.LoggingEvent; -import org.junit.After; -import org.junit.Before; - -public class BasicLoggerTest { - - protected Logger logger = Logger.getLogger("test"); - - protected LoggingEvent loggingEvent; - - protected String loggingDir = System.getProperty("user.home") + "/logs/rocketmq-test"; - - @Before - public void createLoggingEvent() { - loggingEvent = new LoggingEvent(Logger.class.getName(), logger, Level.INFO, - "junit test error", new RuntimeException("createLogging error")); - } - - public String readFile(String file) throws IOException { - StringBuilder stringBuilder = new StringBuilder(); - FileInputStream fileInputStream = new FileInputStream(file); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream)); - String line = bufferedReader.readLine(); - while (line != null) { - stringBuilder.append(line); - stringBuilder.append("\r\n"); - line = bufferedReader.readLine(); - } - bufferedReader.close(); - return stringBuilder.toString(); - } - - @After - public void clean() { - File file = new File(loggingDir); - if (file.exists()) { - File[] files = file.listFiles(); - for (File file1 : files) { - file1.delete(); - } - } - } -} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/InnerLoggerFactoryTest.java b/logging/src/test/java/org/apache/rocketmq/logging/InnerLoggerFactoryTest.java deleted file mode 100644 index c47dba6840a..00000000000 --- a/logging/src/test/java/org/apache/rocketmq/logging/InnerLoggerFactoryTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging; - -import org.apache.rocketmq.logging.inner.Appender; -import org.apache.rocketmq.logging.inner.Level; -import org.apache.rocketmq.logging.inner.Logger; -import org.apache.rocketmq.logging.inner.LoggingBuilder; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; - -public class InnerLoggerFactoryTest extends BasicLoggerTest { - - private ByteArrayOutputStream byteArrayOutputStream; - - public static final String LOGGER = "ConsoleLogger"; - - private PrintStream out; - - @Before - public void initLogger() { - out = System.out; - byteArrayOutputStream = new ByteArrayOutputStream(); - System.setOut(new PrintStream(byteArrayOutputStream)); - - Appender consoleAppender = LoggingBuilder.newAppenderBuilder() - .withConsoleAppender(LoggingBuilder.SYSTEM_OUT) - .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); - - Logger consoleLogger = Logger.getLogger("ConsoleLogger"); - consoleLogger.setAdditivity(false); - consoleLogger.addAppender(consoleAppender); - consoleLogger.setLevel(Level.INFO); - } - - @After - public void fixConsole() { - System.setOut(out); - } - - @Test - public void testInnerLoggerFactory() { - InternalLoggerFactory.setCurrentLoggerType(InternalLoggerFactory.LOGGER_INNER); - - InternalLogger logger1 = InnerLoggerFactory.getLogger(LOGGER); - InternalLogger logger = InternalLoggerFactory.getLogger(LOGGER); - - Assert.assertTrue(logger.getName().equals(logger1.getName())); - - InternalLogger logger2 = InnerLoggerFactory.getLogger(InnerLoggerFactoryTest.class); - InnerLoggerFactory.InnerLogger logger3 = (InnerLoggerFactory.InnerLogger) logger2; - - logger.info("innerLogger inner info Message"); - logger.error("innerLogger inner error Message", new RuntimeException()); - logger.debug("innerLogger inner debug message"); - logger3.info("innerLogger info message"); - logger3.error("logback error message"); - logger3.info("info {}", "hahahah"); - logger3.warn("warn {}", "hahahah"); - logger3.warn("logger3 warn"); - logger3.error("error {}", "hahahah"); - logger3.debug("debug {}", "hahahah"); - - String content = new String(byteArrayOutputStream.toByteArray()); - System.out.println(content); - - Assert.assertTrue(content.contains("InnerLoggerFactoryTest")); - Assert.assertTrue(content.contains("info")); - Assert.assertTrue(content.contains("RuntimeException")); - Assert.assertTrue(!content.contains("debug")); - } -} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/InternalLoggerTest.java b/logging/src/test/java/org/apache/rocketmq/logging/InternalLoggerTest.java deleted file mode 100644 index 50f1dd1c9bd..00000000000 --- a/logging/src/test/java/org/apache/rocketmq/logging/InternalLoggerTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import org.apache.rocketmq.logging.inner.Appender; -import org.apache.rocketmq.logging.inner.Level; -import org.apache.rocketmq.logging.inner.Logger; -import org.apache.rocketmq.logging.inner.LoggingBuilder; -import org.apache.rocketmq.logging.inner.SysLogger; -import org.junit.Assert; -import org.junit.Test; - -public class InternalLoggerTest { - - @Test - public void testInternalLogger() { - SysLogger.setQuietMode(false); - SysLogger.setInternalDebugging(true); - PrintStream out = System.out; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - System.setOut(new PrintStream(byteArrayOutputStream)); - - Appender consoleAppender = LoggingBuilder.newAppenderBuilder() - .withConsoleAppender(LoggingBuilder.SYSTEM_OUT) - .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); - - Logger consoleLogger = Logger.getLogger("ConsoleLogger"); - consoleLogger.setAdditivity(false); - consoleLogger.addAppender(consoleAppender); - consoleLogger.setLevel(Level.INFO); - - Logger.getRootLogger().addAppender(consoleAppender); - - InternalLoggerFactory.setCurrentLoggerType(InternalLoggerFactory.LOGGER_INNER); - InternalLogger logger = InternalLoggerFactory.getLogger(InternalLoggerTest.class); - InternalLogger consoleLogger1 = InternalLoggerFactory.getLogger("ConsoleLogger"); - - consoleLogger1.warn("simple warn {}", 14555); - - logger.info("testInternalLogger"); - consoleLogger1.info("consoleLogger1"); - - System.setOut(out); - consoleAppender.close(); - - String result = new String(byteArrayOutputStream.toByteArray()); - Assert.assertTrue(result.contains("consoleLogger1")); - Assert.assertTrue(result.contains("testInternalLogger")); - } - -} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/Slf4jLoggerFactoryTest.java b/logging/src/test/java/org/apache/rocketmq/logging/Slf4jLoggerFactoryTest.java deleted file mode 100644 index 4bed745b3c4..00000000000 --- a/logging/src/test/java/org/apache/rocketmq/logging/Slf4jLoggerFactoryTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging; - -import ch.qos.logback.classic.joran.JoranConfigurator; -import ch.qos.logback.core.Context; -import ch.qos.logback.core.joran.spi.JoranException; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.slf4j.ILoggerFactory; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.URL; - -public class Slf4jLoggerFactoryTest extends BasicLoggerTest { - - public static final String LOGGER = "Slf4jTestLogger"; - - @Before - public void initLogback() throws JoranException { - InternalLoggerFactory.setCurrentLoggerType(InternalLoggerFactory.LOGGER_SLF4J); - System.setProperty("loggingDir", loggingDir); - ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory(); - JoranConfigurator joranConfigurator = new JoranConfigurator(); - joranConfigurator.setContext((Context) iLoggerFactory); - URL logbackConfigFile = Slf4jLoggerFactoryTest.class.getClassLoader().getResource("logback_test.xml"); - if (logbackConfigFile == null) { - throw new RuntimeException("can't find logback_test.xml"); - } else { - joranConfigurator.doConfigure(logbackConfigFile); - } - } - - @Test - public void testSlf4j() throws IOException { - InternalLogger logger1 = Slf4jLoggerFactory.getLogger(LOGGER); - InternalLogger logger = InternalLoggerFactory.getLogger(LOGGER); - Assert.assertTrue(logger.getName().equals(logger1.getName())); - InternalLogger logger2 = Slf4jLoggerFactory.getLogger(Slf4jLoggerFactoryTest.class); - Slf4jLoggerFactory.Slf4jLogger logger3 = (Slf4jLoggerFactory.Slf4jLogger) logger2; - - String file = loggingDir + "/logback_test.log"; - - logger.info("logback slf4j info Message"); - logger.error("logback slf4j error Message", new RuntimeException("test")); - logger.debug("logback slf4j debug message"); - logger3.info("logback info message"); - logger3.error("logback error message"); - logger3.info("info {}", "hahahah"); - logger3.warn("warn {}", "hahahah"); - logger3.warn("logger3 warn"); - logger3.error("error {}", "hahahah"); - logger3.debug("debug {}", "hahahah"); - String content = readFile(file); - System.out.println(content); - - Assert.assertTrue(content.contains("Slf4jLoggerFactoryTest")); - Assert.assertTrue(content.contains("info")); - Assert.assertTrue(content.contains("RuntimeException")); - Assert.assertTrue(!content.contains("debug")); - } - -} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/AppenderTest.java b/logging/src/test/java/org/apache/rocketmq/logging/inner/AppenderTest.java deleted file mode 100644 index 37ff8bd47f6..00000000000 --- a/logging/src/test/java/org/apache/rocketmq/logging/inner/AppenderTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging.inner; - -import org.apache.rocketmq.logging.BasicLoggerTest; -import org.junit.Assert; -import org.junit.Test; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; - -public class AppenderTest extends BasicLoggerTest { - - @Test - public void testConsole() { - SysLogger.setQuietMode(false); - SysLogger.setInternalDebugging(true); - PrintStream out = System.out; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - System.setOut(new PrintStream(byteArrayOutputStream)); - - Appender consoleAppender = LoggingBuilder.newAppenderBuilder() - .withConsoleAppender(LoggingBuilder.SYSTEM_OUT) - .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); - - LoggingBuilder.ConsoleAppender consoleAppender1 = (LoggingBuilder.ConsoleAppender) consoleAppender; - String target = consoleAppender1.getTarget(); - Assert.assertTrue(target.equals(LoggingBuilder.SYSTEM_OUT)); - - Layout layout = consoleAppender.getLayout(); - Assert.assertTrue(layout instanceof LoggingBuilder.DefaultLayout); - - Logger consoleLogger = Logger.getLogger("ConsoleLogger"); - consoleLogger.setAdditivity(false); - consoleLogger.addAppender(consoleAppender); - consoleLogger.setLevel(Level.INFO); - - Logger.getRootLogger().addAppender(consoleAppender); - Logger.getLogger(AppenderTest.class).info("this is a AppenderTest log"); - - Logger.getLogger("ConsoleLogger").info("console info Message"); - Logger.getLogger("ConsoleLogger").error("console error Message", new RuntimeException()); - Logger.getLogger("ConsoleLogger").debug("console debug message"); - System.setOut(out); - consoleAppender.close(); - - String result = new String(byteArrayOutputStream.toByteArray()); - - Assert.assertTrue(result.contains("info")); - Assert.assertTrue(result.contains("RuntimeException")); - Assert.assertTrue(!result.contains("debug")); - Assert.assertTrue(result.contains("AppenderTest")); - } - - @Test - public void testInnerFile() throws IOException { - String file = loggingDir + "/logger.log"; - - Logger fileLogger = Logger.getLogger("fileLogger"); - - Appender myappender = LoggingBuilder.newAppenderBuilder() - .withDailyFileRollingAppender(file, "'.'yyyy-MM-dd") - .withName("myappender") - .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); - - fileLogger.addAppender(myappender); - - Logger.getLogger("fileLogger").setLevel(Level.INFO); - - Logger.getLogger("fileLogger").info("fileLogger info Message"); - Logger.getLogger("fileLogger").error("fileLogger error Message", new RuntimeException()); - Logger.getLogger("fileLogger").debug("fileLogger debug message"); - - myappender.close(); - - String content = readFile(file); - - System.out.println(content); - - Assert.assertTrue(content.contains("info")); - Assert.assertTrue(content.contains("RuntimeException")); - Assert.assertTrue(!content.contains("debug")); - } - - - - @Test - public void asyncAppenderTest() { - Appender appender = LoggingBuilder.newAppenderBuilder().withAsync(false, 1024) - .withConsoleAppender(LoggingBuilder.SYSTEM_OUT) - .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); - Assert.assertTrue(appender instanceof LoggingBuilder.AsyncAppender); - LoggingBuilder.AsyncAppender asyncAppender = (LoggingBuilder.AsyncAppender) appender; - Assert.assertTrue(!asyncAppender.getBlocking()); - Assert.assertTrue(asyncAppender.getBufferSize() > 0); - } - - @Test - public void testWriteAppender() { - LoggingBuilder.WriterAppender writerAppender = new LoggingBuilder.WriterAppender(); - writerAppender.setImmediateFlush(true); - Assert.assertTrue(writerAppender.getImmediateFlush()); - } - - @Test - public void testFileAppender() throws IOException { - LoggingBuilder.FileAppender fileAppender = new LoggingBuilder.FileAppender( - new LoggingBuilder.SimpleLayout(), loggingDir + "/simple.log", true); - fileAppender.setBufferSize(1024); - int bufferSize = fileAppender.getBufferSize(); - boolean bufferedIO = fileAppender.getBufferedIO(); - Assert.assertTrue(!bufferedIO); - Assert.assertTrue(bufferSize > 0); - Assert.assertTrue(fileAppender.getAppend()); - - LoggingBuilder.RollingFileAppender rollingFileAppender = new LoggingBuilder.RollingFileAppender(); - rollingFileAppender.setImmediateFlush(true); - rollingFileAppender.setMaximumFileSize(1024 * 1024); - rollingFileAppender.setMaxBackupIndex(10); - rollingFileAppender.setAppend(true); - rollingFileAppender.setFile(loggingDir + "/rolling_file.log"); - rollingFileAppender.setName("myRollingFileAppender"); - - rollingFileAppender.activateOptions(); - - Assert.assertTrue(rollingFileAppender.getMaximumFileSize() > 0); - Assert.assertTrue(rollingFileAppender.getMaxBackupIndex() == 10); - } - - @Test - public void testDailyRollingAppender() { - LoggingBuilder.DailyRollingFileAppender dailyRollingFileAppender = new LoggingBuilder.DailyRollingFileAppender(); - dailyRollingFileAppender.setFile(loggingDir + "/daily.log"); - dailyRollingFileAppender.setName("dailyAppender"); - dailyRollingFileAppender.setAppend(true); - dailyRollingFileAppender.setDatePattern("'.'yyyy-mm-dd"); - String datePattern = dailyRollingFileAppender.getDatePattern(); - Assert.assertTrue(datePattern != null); - dailyRollingFileAppender.activateOptions(); - } - -} - - diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/LayoutTest.java b/logging/src/test/java/org/apache/rocketmq/logging/inner/LayoutTest.java deleted file mode 100644 index 66ef18eaeb6..00000000000 --- a/logging/src/test/java/org/apache/rocketmq/logging/inner/LayoutTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging.inner; - -import org.apache.rocketmq.logging.BasicLoggerTest; -import org.junit.Assert; -import org.junit.Test; - -public class LayoutTest extends BasicLoggerTest { - - @Test - public void testSimpleLayout() { - Layout layout = LoggingBuilder.newLayoutBuilder().withSimpleLayout().build(); - String format = layout.format(loggingEvent); - Assert.assertTrue(format.contains("junit")); - } - - @Test - public void testDefaultLayout() { - Layout layout = LoggingBuilder.newLayoutBuilder().withDefaultLayout().build(); - String format = layout.format(loggingEvent); - String contentType = layout.getContentType(); - Assert.assertTrue(contentType.contains("text")); - Assert.assertTrue(format.contains("createLoggingEvent")); - Assert.assertTrue(format.contains("createLogging error")); - Assert.assertTrue(format.contains(Thread.currentThread().getName())); - } - - @Test - public void testLogFormat() { - Layout innerLayout = LoggingBuilder.newLayoutBuilder().withDefaultLayout().build(); - - LoggingEvent loggingEvent = new LoggingEvent(Logger.class.getName(), logger, org.apache.rocketmq.logging.inner.Level.INFO, - "junit test error", null); - String format = innerLayout.format(loggingEvent); - - System.out.println(format); - } -} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerRepositoryTest.java b/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerRepositoryTest.java deleted file mode 100644 index 6a56c20ff7a..00000000000 --- a/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerRepositoryTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging.inner; - -import org.apache.rocketmq.logging.BasicLoggerTest; -import org.junit.Assert; -import org.junit.Test; - -import java.util.Enumeration; - -public class LoggerRepositoryTest extends BasicLoggerTest { - - @Test - public void testLoggerRepository() { - Logger.getRepository().setLogLevel(Level.INFO); - - String file = loggingDir + "/repo.log"; - Logger fileLogger = Logger.getLogger("repoLogger"); - - Appender myappender = LoggingBuilder.newAppenderBuilder() - .withDailyFileRollingAppender(file, "'.'yyyy-MM-dd") - .withName("repoAppender") - .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); - - fileLogger.addAppender(myappender); - Logger.getLogger("repoLogger").setLevel(Level.INFO); - Logger repoLogger = Logger.getRepository().exists("repoLogger"); - Assert.assertTrue(repoLogger != null); - Enumeration currentLoggers = Logger.getRepository().getCurrentLoggers(); - Level logLevel = Logger.getRepository().getLogLevel(); - Assert.assertTrue(logLevel.equals(Level.INFO)); -// Logger.getRepository().shutdown(); - } -} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerTest.java b/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerTest.java deleted file mode 100644 index 4e738e230cd..00000000000 --- a/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggerTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging.inner; - -import org.apache.rocketmq.logging.BasicLoggerTest; -import org.apache.rocketmq.logging.InnerLoggerFactory; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; - -public class LoggerTest extends BasicLoggerTest { - - - @Before - public void init() { - InternalLoggerFactory.setCurrentLoggerType(InnerLoggerFactory.LOGGER_INNER); - } - - @Test - public void testInnerConsoleLogger() throws IOException { - PrintStream out = System.out; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - System.setOut(new PrintStream(byteArrayOutputStream)); - - Appender consoleAppender = LoggingBuilder.newAppenderBuilder() - .withConsoleAppender(LoggingBuilder.SYSTEM_OUT) - .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); - - Logger.getLogger("ConsoleLogger").addAppender(consoleAppender); - Logger.getLogger("ConsoleLogger").setLevel(Level.INFO); - - InternalLogger consoleLogger1 = InternalLoggerFactory.getLogger("ConsoleLogger"); - consoleLogger1.info("console info Message"); - consoleLogger1.error("console error Message", new RuntimeException()); - consoleLogger1.debug("console debug message"); - - consoleLogger1.info("console {} test", "simple"); - consoleLogger1.info("[WATERMARK] Send Queue Size: {} SlowTimeMills: {}", 1, 300); - consoleLogger1.info("new consumer connected, group: {} {} {} channel: {}", "mygroup", "orderly", - "broudcast", new RuntimeException("simple object")); - - System.setOut(out); - consoleAppender.close(); - - String result = new String(byteArrayOutputStream.toByteArray()); - - System.out.println(result); - - Assert.assertTrue(result.contains("info")); - Assert.assertTrue(result.contains("RuntimeException")); - Assert.assertTrue(result.contains("WATERMARK")); - Assert.assertTrue(result.contains("consumer")); - Assert.assertTrue(result.contains("broudcast")); - Assert.assertTrue(result.contains("simple test")); - Assert.assertTrue(!result.contains("debug")); - } - - @Test - public void testInnerFileLogger() throws IOException { - String file = loggingDir + "/inner.log"; - - Logger fileLogger = Logger.getLogger("innerLogger"); - - Appender myappender = LoggingBuilder.newAppenderBuilder() - .withDailyFileRollingAppender(file, "'.'yyyy-MM-dd") - .withName("innerAppender") - .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); - - fileLogger.addAppender(myappender); - fileLogger.setLevel(Level.INFO); - - InternalLogger innerLogger = InternalLoggerFactory.getLogger("innerLogger"); - - innerLogger.info("fileLogger info Message"); - innerLogger.error("fileLogger error Message", new RuntimeException()); - innerLogger.debug("fileLogger debug message"); - - myappender.close(); - - String content = readFile(file); - - System.out.println(content); - - Assert.assertTrue(content.contains("info")); - Assert.assertTrue(content.contains("RuntimeException")); - Assert.assertTrue(!content.contains("debug")); - } - - @After - public void close() { - InternalLoggerFactory.setCurrentLoggerType(null); - } -} diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggingBuilderTest.java b/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggingBuilderTest.java deleted file mode 100644 index 6c816a6815c..00000000000 --- a/logging/src/test/java/org/apache/rocketmq/logging/inner/LoggingBuilderTest.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.logging.inner; - -import org.apache.rocketmq.logging.BasicLoggerTest; -import org.junit.Assert; -import org.junit.Test; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FilenameFilter; -import java.io.PrintStream; - -public class LoggingBuilderTest extends BasicLoggerTest { - - @Test - public void testConsole() { - PrintStream out = System.out; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - System.setOut(new PrintStream(byteArrayOutputStream)); - - Appender consoleAppender = LoggingBuilder.newAppenderBuilder() - .withConsoleAppender(LoggingBuilder.SYSTEM_OUT) - .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); - consoleAppender.doAppend(loggingEvent); - String result = new String(byteArrayOutputStream.toByteArray()); - System.setOut(out); - - Assert.assertTrue(result.contains(loggingEvent.getMessage().toString())); - - } - - @Test - public void testFileAppender() throws InterruptedException { - String logFile = loggingDir + "/file.log"; - Appender rollingFileAppender = LoggingBuilder.newAppenderBuilder().withAsync(false, 102400) - .withFileAppender(logFile).withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); - - for (int i = 0; i < 10; i++) { - rollingFileAppender.doAppend(loggingEvent); - } - rollingFileAppender.close(); - - File file = new File(logFile); - Assert.assertTrue(file.length() > 0); - } - - @Test - public void testRollingFileAppender() throws InterruptedException { - - String rollingFile = loggingDir + "/rolling.log"; - Appender rollingFileAppender = LoggingBuilder.newAppenderBuilder().withAsync(false, 1024) - .withRollingFileAppender(rollingFile, "1024", 5) - .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); - - for (int i = 0; i < 100; i++) { - rollingFileAppender.doAppend(loggingEvent); - } - rollingFileAppender.close(); - - int cc = 0; - for (int i = 0; i < 5; i++) { - File file; - if (i == 0) { - file = new File(rollingFile); - } else { - file = new File(rollingFile + "." + i); - } - if (file.exists() && file.length() > 0) { - cc += 1; - } - } - Assert.assertTrue(cc >= 2); - } - - //@Test - public void testDailyRollingFileAppender() throws InterruptedException { - String rollingFile = loggingDir + "/daily-rolling--222.log"; - Appender rollingFileAppender = LoggingBuilder.newAppenderBuilder().withAsync(false, 1024) - .withDailyFileRollingAppender(rollingFile, "'.'yyyy-MM-dd_HH-mm-ss-SSS") - .withLayout(LoggingBuilder.newLayoutBuilder().withDefaultLayout().build()).build(); - - for (int i = 0; i < 100; i++) { - rollingFileAppender.doAppend(loggingEvent); - } - - rollingFileAppender.close(); - - File file = new File(loggingDir); - String[] list = file.list(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.startsWith("daily-rolling--222.log"); - } - }); - Assert.assertTrue(list.length > 0); - } -} diff --git a/logging/src/test/resources/logback_test.xml b/logging/src/test/resources/logback_test.xml deleted file mode 100644 index c1ab200449c..00000000000 --- a/logging/src/test/resources/logback_test.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - ${loggingDir}/logback_test.log - true - - ${loggingDir}/logback_test.%i.log - - 1 - 5 - - - 10MB - - - %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n - UTF-8 - - - - - - - - - - diff --git a/namesrv/BUILD.bazel b/namesrv/BUILD.bazel new file mode 100644 index 00000000000..fec42eaa3e4 --- /dev/null +++ b/namesrv/BUILD.bazel @@ -0,0 +1,75 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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_fastjson", + "@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_fastjson", + "@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", + ], +) diff --git a/namesrv/pom.xml b/namesrv/pom.xml index 7ecbcd3af6a..93989d5dcdf 100644 --- a/namesrv/pom.xml +++ b/namesrv/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.4-SNAPSHOT + 5.1.3 4.0.0 @@ -27,7 +27,15 @@ rocketmq-namesrv rocketmq-namesrv ${project.version} + + ${basedir}/.. + + + + ${project.groupId} + rocketmq-controller + ${project.groupId} rocketmq-client @@ -41,12 +49,16 @@ rocketmq-srvutil - ch.qos.logback - logback-classic + org.openjdk.jmh + jmh-core + 1.19 + test - org.slf4j - slf4j-api + org.openjdk.jmh + jmh-generator-annprocess + 1.19 + test org.bouncycastle diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java index a6654f271c3..15c65ebec9d 100644 --- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java @@ -16,154 +16,247 @@ */ package org.apache.rocketmq.namesrv; +import java.util.Collections; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RunnableFuture; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import org.apache.rocketmq.common.Configuration; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); + 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 = new ScheduledThreadPoolExecutor(1, + new BasicThreadFactory.Builder().namingPattern("NSScheduledThread").daemon(true).build()); + + private final ScheduledExecutorService scanExecutorService = new ScheduledThreadPoolExecutor(1, + new BasicThreadFactory.Builder().namingPattern("NSScanScheduledThread").daemon(true).build()); - private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl( - "NSScheduledThread")); private final KVConfigManager kvConfigManager; private final RouteInfoManager routeInfoManager; + private RemotingClient remotingClient; private RemotingServer remotingServer; - private BrokerHousekeepingService brokerHousekeepingService; + private final BrokerHousekeepingService brokerHousekeepingService; - private ExecutorService remotingExecutor; + private ExecutorService defaultExecutor; + private ExecutorService clientRequestExecutor; - private Configuration configuration; + 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.routeInfoManager = new RouteInfoManager(); this.brokerHousekeepingService = new BrokerHousekeepingService(this); - this.configuration = new Configuration( - log, - this.namesrvConfig, this.nettyServerConfig - ); + 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(); + } - this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService); + private void startScheduleService() { + this.scanExecutorService.scheduleAtFixedRate(NamesrvController.this.routeInfoManager::scanNotActiveBroker, + 5, this.namesrvConfig.getScanNotActiveBrokerInterval(), TimeUnit.MILLISECONDS); - this.remotingExecutor = - Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_")); + this.scheduledExecutorService.scheduleAtFixedRate(NamesrvController.this.kvConfigManager::printAllPeriodically, + 1, 10, TimeUnit.MINUTES); - this.registerProcessor(); + 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); + } - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + private void initiateThreadExecutors() { + this.defaultThreadPoolQueue = new LinkedBlockingQueue<>(this.namesrvConfig.getDefaultThreadPoolQueueCapacity()); + this.defaultExecutor = new ThreadPoolExecutor(this.namesrvConfig.getDefaultThreadPoolNums(), this.namesrvConfig.getDefaultThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.defaultThreadPoolQueue, new ThreadFactoryImpl("RemotingExecutorThread_")) { + @Override + protected RunnableFuture newTaskFor(final Runnable runnable, final T value) { + return new FutureTaskExt<>(runnable, value); + } + }; + this.clientRequestThreadPoolQueue = new LinkedBlockingQueue<>(this.namesrvConfig.getClientRequestThreadPoolQueueCapacity()); + this.clientRequestExecutor = new ThreadPoolExecutor(this.namesrvConfig.getClientRequestThreadPoolNums(), this.namesrvConfig.getClientRequestThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.clientRequestThreadPoolQueue, new ThreadFactoryImpl("ClientRequestExecutorThread_")) { @Override - public void run() { - NamesrvController.this.routeInfoManager.scanNotActiveBroker(); + protected RunnableFuture newTaskFor(final Runnable runnable, final T value) { + return new FutureTaskExt<>(runnable, value); } - }, 5, 10, TimeUnit.SECONDS); + }; + } + + private void initiateSslContext() { + if (TlsSystemConfig.tlsMode == TlsMode.DISABLED) { + return; + } + + String[] watchFiles = {TlsSystemConfig.tlsServerCertPath, TlsSystemConfig.tlsServerKeyPath, TlsSystemConfig.tlsServerTrustCertPath}; - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + FileWatchService.Listener listener = new FileWatchService.Listener() { + boolean certChanged, keyChanged = false; @Override - public void run() { - NamesrvController.this.kvConfigManager.printAllPeriodically(); + 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(); + } } - }, 1, 10, TimeUnit.MINUTES); + }; - 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() { - ((NettyRemotingServer) remotingServer).loadSslContext(); - } - }); - } catch (Exception e) { - log.warn("FileWatchService created error, can't load the certificate dynamically"); + 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(); } } - return true; + if (slowTimeMills < 0) { + slowTimeMills = 0; + } + + return slowTimeMills; } private void registerProcessor() { if (namesrvConfig.isClusterTest()) { - this.remotingServer.registerDefaultProcessor(new ClusterTestRequestProcessor(this, namesrvConfig.getProductEnvName()), - this.remotingExecutor); + 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.remotingExecutor); + 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.remotingExecutor.shutdown(); + this.defaultExecutor.shutdown(); + this.clientRequestExecutor.shutdown(); this.scheduledExecutorService.shutdown(); + this.scanExecutorService.shutdown(); + this.routeInfoManager.shutdown(); if (this.fileWatchService != null) { this.fileWatchService.shutdown(); @@ -190,6 +283,10 @@ public RemotingServer getRemotingServer() { return remotingServer; } + public RemotingClient getRemotingClient() { + return remotingClient; + } + public void setRemotingServer(RemotingServer remotingServer) { this.remotingServer = remotingServer; } diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvStartup.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvStartup.java index f6a26d6888b..deb7c75d189 100644 --- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvStartup.java +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvStartup.java @@ -16,49 +16,49 @@ */ package org.apache.rocketmq.namesrv; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.joran.JoranConfigurator; -import ch.qos.logback.core.joran.spi.JoranException; import java.io.BufferedInputStream; -import java.io.FileInputStream; -import java.io.IOException; 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.commons.cli.PosixParser; +import org.apache.rocketmq.common.ControllerConfig; import org.apache.rocketmq.common.MQVersion; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; 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; -import org.slf4j.LoggerFactory; public class NamesrvStartup { - private static InternalLogger log; + private final static Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); + private final static Logger logConsole = LoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_LOGGER_NAME); private static Properties properties = null; - private static CommandLine commandLine = 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 { - NamesrvController controller = createNamesrvController(args); - start(controller); - String tip = "The Name Server boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer(); - log.info(tip); - System.out.printf("%s%n", tip); + parseCommandlineAndConfigFile(args); + NamesrvController controller = createAndStartNamesrvController(); return controller; } catch (Throwable e) { e.printStackTrace(); @@ -68,29 +68,45 @@ public static NamesrvController main0(String[] args) { return null; } - public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException { + 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)); - //PackageConflictDetect.detectFastjson(); Options options = ServerUtil.buildCommandlineOptions(new Options()); - commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser()); + CommandLine commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new DefaultParser()); if (null == commandLine) { System.exit(-1); - return null; + return; } - final NamesrvConfig namesrvConfig = new NamesrvConfig(); - final NettyServerConfig nettyServerConfig = new NettyServerConfig(); + 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(new FileInputStream(file)); + 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(); + MixAll.properties2Object(properties, controllerConfig); + } namesrvConfig.setConfigStorePath(file); System.out.printf("load config properties file OK, %s%n", file); @@ -98,36 +114,42 @@ public static NamesrvController createNamesrvController(String[] args) throws IO } } + MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig); if (commandLine.hasOption('p')) { - InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME); - MixAll.printObjectProperties(console, namesrvConfig); - MixAll.printObjectProperties(console, nettyServerConfig); + MixAll.printObjectProperties(logConsole, namesrvConfig); + MixAll.printObjectProperties(logConsole, nettyServerConfig); + MixAll.printObjectProperties(logConsole, nettyClientConfig); + if (namesrvConfig.isEnableControllerInNamesrv()) { + MixAll.printObjectProperties(logConsole, controllerConfig); + } System.exit(0); } - MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig); - 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); - LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); - JoranConfigurator configurator = new JoranConfigurator(); - configurator.setContext(lc); - lc.reset(); - configurator.doConfigure(namesrvConfig.getRocketmqHome() + "/conf/logback_namesrv.xml"); + } - log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); + public static NamesrvController createAndStartNamesrvController() throws Exception { - MixAll.printObjectProperties(log, namesrvConfig); - MixAll.printObjectProperties(log, nettyServerConfig); + 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; + } - final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig); + public static NamesrvController createNamesrvController() { + final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig, nettyClientConfig); // remember all configs to prevent discard controller.getConfiguration().registerConfig(properties); - return controller; } @@ -143,12 +165,9 @@ public static NamesrvController start(final NamesrvController controller) throws System.exit(-3); } - Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable() { - @Override - public Void call() throws Exception { - controller.shutdown(); - return null; - } + Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, (Callable) () -> { + controller.shutdown(); + return null; })); controller.start(); @@ -156,10 +175,53 @@ public Void call() throws Exception { 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); @@ -168,7 +230,6 @@ public static Options buildCommandlineOptions(final Options options) { opt = new Option("p", "printConfigItem", false, "Print all config items"); opt.setRequired(false); options.addOption(opt); - return options; } diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/kvconfig/KVConfigManager.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/kvconfig/KVConfigManager.java index f35115922d0..5c8a3d15027 100644 --- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/kvconfig/KVConfigManager.java +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/kvconfig/KVConfigManager.java @@ -18,24 +18,24 @@ import java.io.IOException; import java.util.HashMap; -import java.util.Iterator; 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.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.protocol.body.KVTable; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); + 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>(); + new HashMap<>(); public KVConfigManager(NamesrvController namesrvController) { this.namesrvController = namesrvController; @@ -64,7 +64,7 @@ public void putKVConfig(final String namespace, final String key, final String v try { HashMap kvTable = this.configTable.get(namespace); if (null == kvTable) { - kvTable = new HashMap(); + kvTable = new HashMap<>(); this.configTable.put(namespace, kvTable); log.info("putKVConfig create new Namespace {}", namespace); } @@ -177,15 +177,10 @@ public void printAllPeriodically() { { log.info("configTable SIZE: {}", this.configTable.size()); - Iterator>> it = - this.configTable.entrySet().iterator(); - while (it.hasNext()) { - Entry> next = it.next(); - Iterator> itSub = next.getValue().entrySet().iterator(); - while (itSub.hasNext()) { - Entry nextSub = itSub.next(); + for (Entry> next : this.configTable.entrySet()) { + for (Entry nextSub : next.getValue().entrySet()) { log.info("configTable NS: {} Key: {} Value: {}", next.getKey(), nextSub.getKey(), - nextSub.getValue()); + nextSub.getValue()); } } } diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/ClientRequestProcessor.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/ClientRequestProcessor.java new file mode 100644 index 00000000000..97a132e2343 --- /dev/null +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/ClientRequestProcessor.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.serializer.SerializerFeature; +import io.netty.channel.ChannelHandlerContext; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +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; + +public class ClientRequestProcessor implements NettyRequestProcessor { + + private static Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); + + protected NamesrvController namesrvController; + private long startupTimeMillis; + + private AtomicBoolean needCheckNamesrvReady = new AtomicBoolean(true); + + 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 = needCheckNamesrvReady.get() && 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) { + //topic route info register success ,so disable namesrvReady check + if (needCheckNamesrvReady.get()) { + needCheckNamesrvReady.set(false); + } + + 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 = requestHeader.getAcceptStandardJsonOnly(); + if (request.getVersion() >= MQVersion.Version.V4_9_4.ordinal() || null != standardJsonOnly && standardJsonOnly) { + content = topicRouteData.encode(SerializerFeature.BrowserCompatible, + SerializerFeature.QuoteFieldNames, SerializerFeature.SkipTransientField, + SerializerFeature.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; + } +} diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/ClusterTestRequestProcessor.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/ClusterTestRequestProcessor.java index a58a3b97fc2..725c5e633eb 100644 --- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/ClusterTestRequestProcessor.java +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/ClusterTestRequestProcessor.java @@ -20,19 +20,19 @@ 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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; import org.apache.rocketmq.common.namesrv.NamesrvUtil; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.header.namesrv.GetRouteInfoRequestHeader; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; +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 DefaultRequestProcessor { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); +public class ClusterTestRequestProcessor extends ClientRequestProcessor { + private static final Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); private final DefaultMQAdminExt adminExt; private final String productEnvName; diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java index bde03489d36..fada0efd774 100644 --- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java @@ -18,50 +18,57 @@ import io.netty.channel.ChannelHandlerContext; import java.io.UnsupportedEncodingException; +import java.util.List; import java.util.Properties; import java.util.concurrent.atomic.AtomicLong; -import org.apache.rocketmq.common.DataVersion; +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.help.FAQUrl; -import org.apache.rocketmq.common.protocol.header.namesrv.AddWritePermOfBrokerRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.AddWritePermOfBrokerResponseHeader; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; import org.apache.rocketmq.common.namesrv.NamesrvUtil; -import org.apache.rocketmq.common.namesrv.RegisterBrokerResult; -import org.apache.rocketmq.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.body.RegisterBrokerBody; -import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper; -import org.apache.rocketmq.common.protocol.header.GetTopicsByClusterRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.DeleteKVConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.DeleteTopicFromNamesrvRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.GetKVConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.GetKVConfigResponseHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.GetKVListByNamespaceRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.GetRouteInfoRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.PutKVConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.QueryDataVersionRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.QueryDataVersionResponseHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.RegisterBrokerRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.RegisterBrokerResponseHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.UnRegisterBrokerRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.WipeWritePermOfBrokerRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.WipeWritePermOfBrokerResponseHeader; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; +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.AsyncNettyRequestProcessor; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; +import org.apache.rocketmq.remoting.protocol.DataVersion; import org.apache.rocketmq.remoting.protocol.RemotingCommand; - -public class DefaultRequestProcessor extends AsyncNettyRequestProcessor implements NettyRequestProcessor { - private static InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); +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.GetRemoteClientConfigBody; +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; @@ -80,7 +87,6 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, request); } - switch (request.getCode()) { case RequestCode.PUT_KV_CONFIG: return this.putKVConfig(ctx, request); @@ -89,18 +95,15 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, case RequestCode.DELETE_KV_CONFIG: return this.deleteKVConfig(ctx, request); case RequestCode.QUERY_DATA_VERSION: - return queryBrokerTopicConfig(ctx, request); + return this.queryBrokerTopicConfig(ctx, request); case RequestCode.REGISTER_BROKER: - Version brokerVersion = MQVersion.value2Version(request.getVersion()); - if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) { - return this.registerBrokerWithFilterServer(ctx, request); - } else { - return this.registerBroker(ctx, request); - } + return this.registerBroker(ctx, request); case RequestCode.UNREGISTER_BROKER: return this.unregisterBroker(ctx, request); - case RequestCode.GET_ROUTEINFO_BY_TOPIC: - return this.getRouteInfoByTopic(ctx, request); + case RequestCode.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: @@ -108,9 +111,11 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, case RequestCode.ADD_WRITE_PERM_OF_BROKER: return this.addWritePermOfBroker(ctx, request); case RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER: - return getAllTopicListFromNameserver(ctx, request); + return this.getAllTopicListFromNameserver(ctx, request); case RequestCode.DELETE_TOPIC_IN_NAMESRV: - return deleteTopicInNamesrv(ctx, request); + 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: @@ -127,10 +132,12 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, return this.updateConfig(ctx, request); case RequestCode.GET_NAMESRV_CONFIG: return this.getConfig(ctx, request); + case RequestCode.GET_CLIENT_CONFIG: + return this.getClientConfigs(ctx, request); default: - break; + String error = " request type " + request.getCode() + " not supported"; + return RemotingCommand.createResponseCommand(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED, error); } - return null; } @Override @@ -200,8 +207,8 @@ public RemotingCommand deleteKVConfig(ChannelHandlerContext ctx, return response; } - public RemotingCommand registerBrokerWithFilterServer(ChannelHandlerContext ctx, RemotingCommand request) - throws RemotingCommandException { + 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 = @@ -213,17 +220,17 @@ public RemotingCommand registerBrokerWithFilterServer(ChannelHandlerContext ctx, return response; } - RegisterBrokerBody registerBrokerBody = new RegisterBrokerBody(); + TopicConfigSerializeWrapper topicConfigWrapper = null; + List filterServerList = null; - if (request.getBody() != null) { - try { - registerBrokerBody = RegisterBrokerBody.decode(request.getBody(), requestHeader.isCompressed()); - } catch (Exception e) { - throw new RemotingCommandException("Failed to decode RegisterBrokerBody", e); - } + 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.getTopicConfigSerializeWrapper().getDataVersion().setCounter(new AtomicLong(0)); - registerBrokerBody.getTopicConfigSerializeWrapper().getDataVersion().setTimestamp(0); + // RegisterBrokerBody of old version only contains TopicConfig. + topicConfigWrapper = extractRegisterTopicConfigFromRequest(request); } RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker( @@ -232,21 +239,82 @@ public RemotingCommand registerBrokerWithFilterServer(ChannelHandlerContext ctx, requestHeader.getBrokerName(), requestHeader.getBrokerId(), requestHeader.getHaServerAddr(), - registerBrokerBody.getTopicConfigSerializeWrapper(), - registerBrokerBody.getFilterServerList(), - ctx.channel()); + 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()); - byte[] jsonValue = this.namesrvController.getKvConfigManager().getKVListByNamespace(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG); - response.setBody(jsonValue); + 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) { @@ -267,13 +335,13 @@ public RemotingCommand queryBrokerTopicConfig(ChannelHandlerContext ctx, 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(requestHeader.getBrokerAddr(), dataVersion); - if (!changed) { - this.namesrvController.getRouteInfoManager().updateBrokerInfoUpdateTimestamp(requestHeader.getBrokerAddr()); - } + Boolean changed = this.namesrvController.getRouteInfoManager().isBrokerTopicConfigChanged(clusterName, brokerAddr, dataVersion); + this.namesrvController.getRouteInfoManager().updateBrokerInfoUpdateTimestamp(clusterName, brokerAddr); - DataVersion nameSeverDataVersion = this.namesrvController.getRouteInfoManager().queryBrokerTopicConfig(requestHeader.getBrokerAddr()); + DataVersion nameSeverDataVersion = this.namesrvController.getRouteInfoManager().queryBrokerTopicConfig(clusterName, brokerAddr); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); @@ -284,99 +352,40 @@ public RemotingCommand queryBrokerTopicConfig(ChannelHandlerContext ctx, 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); + public RemotingCommand unregisterBroker(ChannelHandlerContext ctx, + RemotingCommand request) throws RemotingCommandException { + final RemotingCommand response = RemotingCommand.createResponseCommand(null); + final UnRegisterBrokerRequestHeader requestHeader = (UnRegisterBrokerRequestHeader) request.decodeCommandCustomHeader(UnRegisterBrokerRequestHeader.class); - if (!checksum(ctx, request, requestHeader)) { + 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("crc32 not match"); + response.setRemark(null); return response; } - - 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(0); - } - - RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker( - requestHeader.getClusterName(), - requestHeader.getBrokerAddr(), - requestHeader.getBrokerName(), - requestHeader.getBrokerId(), - requestHeader.getHaServerAddr(), - topicConfigWrapper, - null, - ctx.channel() - ); - - responseHeader.setHaServerAddr(result.getHaServerAddr()); - responseHeader.setMasterAddr(result.getMasterAddr()); - - byte[] jsonValue = this.namesrvController.getKvConfigManager().getKVListByNamespace(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG); - response.setBody(jsonValue); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } - public RemotingCommand unregisterBroker(ChannelHandlerContext ctx, + public RemotingCommand brokerHeartbeat(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); - final UnRegisterBrokerRequestHeader requestHeader = - (UnRegisterBrokerRequestHeader) request.decodeCommandCustomHeader(UnRegisterBrokerRequestHeader.class); + final BrokerHeartbeatRequestHeader requestHeader = + (BrokerHeartbeatRequestHeader) request.decodeCommandCustomHeader(BrokerHeartbeatRequestHeader.class); - this.namesrvController.getRouteInfoManager().unregisterBroker( - requestHeader.getClusterName(), - requestHeader.getBrokerAddr(), - requestHeader.getBrokerName(), - requestHeader.getBrokerId()); + + this.namesrvController.getRouteInfoManager().updateBrokerInfoUpdateTimestamp(requestHeader.getClusterName(), requestHeader.getBrokerAddr()); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; } - 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) { - if (this.namesrvController.getNamesrvConfig().isOrderMessageEnable()) { - String orderTopicConf = - this.namesrvController.getKvConfigManager().getKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG, - requestHeader.getTopic()); - topicRouteData.setOrderTopicConf(orderTopicConf); - } - - byte[] content = topicRouteData.encode(); - response.setBody(content); - response.setCode(ResponseCode.SUCCESS); - response.setRemark(null); - return response; - } - - response.setCode(ResponseCode.TOPIC_NOT_EXIST); - response.setRemark("No topic route info in name server for the topic: " + requestHeader.getTopic() - + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL)); - return response; - } - private RemotingCommand getBrokerClusterInfo(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); - byte[] content = this.namesrvController.getRouteInfoManager().getAllClusterInfo(); + byte[] content = this.namesrvController.getRouteInfoManager().getAllClusterInfo().encode(); response.setBody(content); response.setCode(ResponseCode.SUCCESS); @@ -395,9 +404,9 @@ private RemotingCommand wipeWritePermOfBroker(ChannelHandlerContext ctx, if (ctx != null) { log.info("wipe write perm of broker[{}], client: {}, {}", - requestHeader.getBrokerName(), - RemotingHelper.parseChannelRemoteAddr(ctx.channel()), - wipeTopicCnt); + requestHeader.getBrokerName(), + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), + wipeTopicCnt); } responseHeader.setWipeTopicCount(wipeTopicCnt); @@ -406,7 +415,8 @@ private RemotingCommand wipeWritePermOfBroker(ChannelHandlerContext ctx, return response; } - private RemotingCommand addWritePermOfBroker(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { + 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); @@ -414,9 +424,9 @@ private RemotingCommand addWritePermOfBroker(ChannelHandlerContext ctx, Remoting int addTopicCnt = this.namesrvController.getRouteInfoManager().addWritePermOfBrokerByLock(requestHeader.getBrokerName()); log.info("add write perm of broker[{}], client: {}, {}", - requestHeader.getBrokerName(), - RemotingHelper.parseChannelRemoteAddr(ctx.channel()), - addTopicCnt); + requestHeader.getBrokerName(), + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), + addTopicCnt); responseHeader.setAddTopicCount(addTopicCnt); response.setCode(ResponseCode.SUCCESS); @@ -426,10 +436,33 @@ private RemotingCommand addWritePermOfBroker(ChannelHandlerContext ctx, Remoting 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); - byte[] body = this.namesrvController.getRouteInfoManager().getAllTopicList(); + 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.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; @@ -441,7 +474,12 @@ private RemotingCommand deleteTopicInNamesrv(ChannelHandlerContext ctx, final DeleteTopicFromNamesrvRequestHeader requestHeader = (DeleteTopicFromNamesrvRequestHeader) request.decodeCommandCustomHeader(DeleteTopicFromNamesrvRequestHeader.class); - this.namesrvController.getRouteInfoManager().deleteTopic(requestHeader.getTopic()); + 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); @@ -474,19 +512,28 @@ private RemotingCommand getTopicsByCluster(ChannelHandlerContext ctx, final GetTopicsByClusterRequestHeader requestHeader = (GetTopicsByClusterRequestHeader) request.decodeCommandCustomHeader(GetTopicsByClusterRequestHeader.class); - byte[] body = this.namesrvController.getRouteInfoManager().getTopicsByCluster(requestHeader.getCluster()); + boolean enableTopicList = namesrvController.getNamesrvConfig().isEnableTopicList(); + if (enableTopicList) { + TopicList topicsByCluster = this.namesrvController.getRouteInfoManager().getTopicsByCluster(requestHeader.getCluster()); + byte[] body = topicsByCluster.encode(); + + response.setBody(body); + response.setCode(ResponseCode.SUCCESS); + response.setRemark(null); + } else { + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark("disable"); + } - response.setBody(body); - response.setCode(ResponseCode.SUCCESS); - response.setRemark(null); return response; } private RemotingCommand getSystemTopicListFromNs(ChannelHandlerContext ctx, - RemotingCommand request) throws RemotingCommandException { + RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); - byte[] body = this.namesrvController.getRouteInfoManager().getSystemTopicList(); + TopicList systemTopicList = this.namesrvController.getRouteInfoManager().getSystemTopicList(); + byte[] body = systemTopicList.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); @@ -495,38 +542,61 @@ private RemotingCommand getSystemTopicListFromNs(ChannelHandlerContext ctx, } private RemotingCommand getUnitTopicList(ChannelHandlerContext ctx, - RemotingCommand request) throws RemotingCommandException { + RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); - byte[] body = this.namesrvController.getRouteInfoManager().getUnitTopics(); + 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"); + } - response.setBody(body); - response.setCode(ResponseCode.SUCCESS); - response.setRemark(null); return response; } private RemotingCommand getHasUnitSubTopicList(ChannelHandlerContext ctx, - RemotingCommand request) throws RemotingCommandException { + RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); - byte[] body = this.namesrvController.getRouteInfoManager().getHasUnitSubTopicList(); + 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"); + } - response.setBody(body); - response.setCode(ResponseCode.SUCCESS); - response.setRemark(null); return response; } - private RemotingCommand getHasUnitSubUnUnitTopicList(ChannelHandlerContext ctx, RemotingCommand request) - throws RemotingCommandException { + private RemotingCommand getHasUnitSubUnUnitTopicList(ChannelHandlerContext ctx, RemotingCommand request) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); - byte[] body = this.namesrvController.getRouteInfoManager().getHasUnitSubUnUnitTopicList(); + 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"); + } - response.setBody(body); - response.setCode(ResponseCode.SUCCESS); - response.setRemark(null); return response; } @@ -557,6 +627,12 @@ private RemotingCommand updateConfig(ChannelHandlerContext ctx, RemotingCommand return response; } + if (properties.containsKey("kvConfigPath") || properties.containsKey("configStorePath")) { + response.setCode(ResponseCode.NO_PERMISSION); + response.setRemark("Can not update config path"); + return response; + } + this.namesrvController.getConfiguration().update(properties); } @@ -569,7 +645,28 @@ private RemotingCommand getConfig(ChannelHandlerContext ctx, RemotingCommand req final RemotingCommand response = RemotingCommand.createResponseCommand(null); String content = this.namesrvController.getConfiguration().getAllConfigsFormatString(); - if (content != null && content.length() > 0) { + if (StringUtils.isNotBlank(content)) { + try { + 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 RemotingCommand getClientConfigs(ChannelHandlerContext ctx, RemotingCommand request) { + final RemotingCommand response = RemotingCommand.createResponseCommand(null); + final GetRemoteClientConfigBody body = GetRemoteClientConfigBody.decode(request.getBody(), GetRemoteClientConfigBody.class); + + String content = this.namesrvController.getConfiguration().getClientConfigsFormatString(body.getKeys()); + if (StringUtils.isNotBlank(content)) { try { response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/route/ZoneRouteRPCHook.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/route/ZoneRouteRPCHook.java new file mode 100644 index 00000000000..4983c88c8af --- /dev/null +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/route/ZoneRouteRPCHook.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()) { + //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(); + if (brokerData.getBrokerAddrs() == null) { + continue; + } + brokerData.getBrokerAddrs().values() + .forEach(brokerAddr -> topicRouteData.getFilterServerTable().remove(brokerAddr)); + } + } + return topicRouteData; + } +} diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/BatchUnregistrationService.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/BatchUnregistrationService.java new file mode 100644 index 00000000000..02cc722a159 --- /dev/null +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/BatchUnregistrationService.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/BrokerHousekeepingService.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/BrokerHousekeepingService.java index fedb4ae610f..80d9939923f 100644 --- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/BrokerHousekeepingService.java +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/BrokerHousekeepingService.java @@ -17,14 +17,11 @@ package org.apache.rocketmq.namesrv.routeinfo; import io.netty.channel.Channel; -import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.remoting.ChannelEventListener; public class BrokerHousekeepingService implements ChannelEventListener { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); + private final NamesrvController namesrvController; public BrokerHousekeepingService(NamesrvController namesrvController) { @@ -37,16 +34,16 @@ public void onChannelConnect(String remoteAddr, Channel channel) { @Override public void onChannelClose(String remoteAddr, Channel channel) { - this.namesrvController.getRouteInfoManager().onChannelDestroy(remoteAddr, channel); + this.namesrvController.getRouteInfoManager().onChannelDestroy(channel); } @Override public void onChannelException(String remoteAddr, Channel channel) { - this.namesrvController.getRouteInfoManager().onChannelDestroy(remoteAddr, channel); + this.namesrvController.getRouteInfoManager().onChannelDestroy(channel); } @Override public void onChannelIdle(String remoteAddr, Channel channel) { - this.namesrvController.getRouteInfoManager().onChannelDestroy(remoteAddr, channel); + this.namesrvController.getRouteInfoManager().onChannelDestroy(channel); } } diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManager.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManager.java index 982d5439469..ac27d76ce1a 100644 --- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManager.java +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManager.java @@ -16,7 +16,10 @@ */ 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; @@ -25,79 +28,190 @@ 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.rocketmq.common.DataVersion; +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.protocol.RequestCode; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.namesrv.RegisterBrokerResult; -import org.apache.rocketmq.common.protocol.body.ClusterInfo; -import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper; -import org.apache.rocketmq.common.protocol.body.TopicList; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; +import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.common.sysflag.TopicSysFlag; -import org.apache.rocketmq.remoting.common.RemotingUtil; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); - private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2; + private static final Logger log = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); + private final static long DEFAULT_BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2; private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private final HashMap> topicQueueTable; - private final HashMap brokerAddrTable; - private final HashMap> clusterAddrTable; - private final HashMap brokerLiveTable; - private final HashMap/* Filter Server */> filterServerTable; + 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 RouteInfoManager() { - this.topicQueueTable = new HashMap>(1024); - this.brokerAddrTable = new HashMap(128); - this.clusterAddrTable = new HashMap>(32); - this.brokerLiveTable = new HashMap(256); - this.filterServerTable = new HashMap>(256); + public boolean submitUnRegisterBrokerRequest(UnRegisterBrokerRequestHeader unRegisterRequest) { + return this.unRegisterService.submit(unRegisterRequest); } - public byte[] getAllClusterInfo() { + // 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.encode(); + 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)) { + 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 { - try { - this.lock.writeLock().lockInterruptibly(); - this.topicQueueTable.remove(topic); - } finally { - this.lock.writeLock().unlock(); + 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 byte[] getAllTopicList() { + public TopicList getAllTopicList() { TopicList topicList = new TopicList(); try { - try { - this.lock.readLock().lockInterruptibly(); - topicList.getTopicList().addAll(this.topicQueueTable.keySet()); - } finally { - this.lock.readLock().unlock(); - } + 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.encode(); + 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( @@ -106,110 +220,211 @@ public RegisterBrokerResult registerBroker( 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 { - try { - this.lock.writeLock().lockInterruptibly(); + this.lock.writeLock().lockInterruptibly(); - Set brokerNames = this.clusterAddrTable.get(clusterName); - if (null == brokerNames) { - brokerNames = new HashSet(); - this.clusterAddrTable.put(clusterName, brokerNames); - } - brokerNames.add(brokerName); + //init or update the cluster info + Set brokerNames = ConcurrentHashMapUtils.computeIfAbsent((ConcurrentHashMap>) this.clusterAddrTable, clusterName, k -> new HashSet<>()); + brokerNames.add(brokerName); - boolean registerFirst = false; + 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); - } - Map brokerAddrsMap = brokerData.getBrokerAddrs(); - //Switch slave to master: first remove <1, IP:PORT> in namesrv, then add <0, IP:PORT> - //The same IP:PORT must only have one record in brokerAddrTable - Iterator> it = brokerAddrsMap.entrySet().iterator(); - while (it.hasNext()) { - Entry item = it.next(); - if (null != brokerAddr && brokerAddr.equals(item.getValue()) && brokerId != item.getKey()) { - it.remove(); + 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("Registered 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; } } + } - String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr); - registerFirst = registerFirst || (null == oldAddr); - - if (null != topicConfigWrapper - && MixAll.MASTER_ID == brokerId) { - if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion()) - || registerFirst) { - ConcurrentMap tcTable = - topicConfigWrapper.getTopicConfigTable(); - if (tcTable != null) { - for (Map.Entry entry : tcTable.entrySet()) { - this.createAndUpdateQueueData(brokerName, entry.getValue()); + 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) { + for (Map.Entry entry : tcTable.entrySet()) { + if (registerFirst || this.isTopicConfigChanged(clusterName, brokerAddr, + topicConfigWrapper.getDataVersion(), brokerName, + entry.getValue().getTopicName())) { + final TopicConfig topicConfig = entry.getValue(); + if (isPrimeSlave) { + // Wipe write perm for prime slave + topicConfig.setPerm(topicConfig.getPerm() & (~PermName.PERM_WRITE)); } + this.createAndUpdateQueueData(brokerName, topicConfig); } } } - BrokerLiveInfo prevBrokerLiveInfo = this.brokerLiveTable.put(brokerAddr, - new BrokerLiveInfo( - System.currentTimeMillis(), - topicConfigWrapper.getDataVersion(), - channel, - haServerAddr)); - if (null == prevBrokerLiveInfo) { - log.info("new broker registered, {} HAServer: {}", brokerAddr, haServerAddr); + if (this.isBrokerTopicConfigChanged(clusterName, brokerAddr, topicConfigWrapper.getDataVersion()) || registerFirst) { + TopicConfigAndMappingSerializeWrapper mappingSerializeWrapper = TopicConfigAndMappingSerializeWrapper.from(topicConfigWrapper); + Map topicQueueMappingInfoMap = mappingSerializeWrapper.getTopicQueueMappingInfoMap(); + //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()); + } } + } - if (filterServerList != null) { - if (filterServerList.isEmpty()) { - this.filterServerTable.remove(brokerAddr); - } else { - this.filterServerTable.put(brokerAddr, filterServerList); - } + 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) { - BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.get(masterAddr); - if (brokerLiveInfo != null) { - result.setHaServerAddr(brokerLiveInfo.getHaServerAddr()); - result.setMasterAddr(masterAddr); - } + 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); } } - } finally { - this.lock.writeLock().unlock(); + } + + 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; } - public boolean isBrokerTopicConfigChanged(final String brokerAddr, final DataVersion dataVersion) { - DataVersion prev = queryBrokerTopicConfig(brokerAddr); + 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 DataVersion queryBrokerTopicConfig(final String brokerAddr) { - BrokerLiveInfo prev = this.brokerLiveTable.get(brokerAddr); + 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 brokerAddr) { - BrokerLiveInfo prev = this.brokerLiveTable.get(brokerAddr); + 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()); } @@ -223,81 +438,75 @@ private void createAndUpdateQueueData(final String brokerName, final TopicConfig queueData.setPerm(topicConfig.getPerm()); queueData.setTopicSysFlag(topicConfig.getTopicSysFlag()); - List queueDataList = this.topicQueueTable.get(topicConfig.getTopicName()); - if (null == queueDataList) { - queueDataList = new LinkedList(); - queueDataList.add(queueData); - this.topicQueueTable.put(topicConfig.getTopicName(), queueDataList); + 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 { - boolean addNewOne = true; - - Iterator it = queueDataList.iterator(); - while (it.hasNext()) { - QueueData qd = it.next(); - if (qd.getBrokerName().equals(brokerName)) { - if (qd.equals(queueData)) { - addNewOne = false; - } else { - log.info("topic changed, {} OLD: {} NEW: {}", topicConfig.getTopicName(), qd, - queueData); - it.remove(); - } - } - } - - if (addNewOne) { - queueDataList.add(queueData); + 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) { - return operateWritePermOfBrokerByLock(brokerName, RequestCode.WIPE_WRITE_PERM_OF_BROKER); - } + 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); + } - public int addWritePermOfBrokerByLock(final String brokerName) { - return operateWritePermOfBrokerByLock(brokerName, RequestCode.ADD_WRITE_PERM_OF_BROKER); + return 0; } - private int operateWritePermOfBrokerByLock(final String brokerName, final int requestCode) { + public int addWritePermOfBrokerByLock(final String brokerName) { try { try { this.lock.writeLock().lockInterruptibly(); - return operateWritePermOfBroker(brokerName, requestCode); + return operateWritePermOfBroker(brokerName, RequestCode.ADD_WRITE_PERM_OF_BROKER); } finally { this.lock.writeLock().unlock(); } } catch (Exception e) { - log.error("operateWritePermOfBrokerByLock Exception", e); + log.error("wipeWritePermOfBrokerByLock Exception", e); } - return 0; } - private int operateWritePermOfBroker(final String brokerName, final int requestCode) { int topicCnt = 0; - for (Entry> entry : this.topicQueueTable.entrySet()) { - List qdList = entry.getValue(); - - for (QueueData qd : qdList) { - if (qd.getBrokerName().equals(brokerName)) { - 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++; - } + + 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; } @@ -306,26 +515,50 @@ public void unregisterBroker( 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 { - try { - this.lock.writeLock().lockInterruptibly(); - BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.remove(brokerAddr); + 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", - brokerAddr + brokerAddrInfo ); - this.filterServerTable.remove(brokerAddr); + this.filterServerTable.remove(brokerAddrInfo); boolean removeBrokerName = false; + boolean isMinBrokerIdChanged = false; BrokerData brokerData = this.brokerAddrTable.get(brokerName); if (null != brokerData) { - String addr = brokerData.getBrokerAddrs().remove(brokerId); + 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 {}, {}", - addr != null ? "OK" : "Failed", - brokerAddr + removed ? "OK" : "Failed", + brokerAddrInfo ); - if (brokerData.getBrokerAddrs().isEmpty()) { this.brokerAddrTable.remove(brokerName); log.info("unregisterBroker, remove name from brokerAddrTable OK, {}", @@ -333,6 +566,9 @@ public void unregisterBroker( ); removeBrokerName = true; + } else if (isMinBrokerIdChanged) { + needNotifyBrokerMap.put(brokerName, new BrokerStatusChangeInfo( + brokerData.getBrokerAddrs(), brokerAddr, null)); } } @@ -351,88 +587,169 @@ public void unregisterBroker( ); } } - this.removeTopicByBrokerName(brokerName); + removedBroker.add(brokerName); + } else { + reducedBroker.add(brokerName); } - } finally { - this.lock.writeLock().unlock(); + } + + 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 removeTopicByBrokerName(final String brokerName) { - Iterator>> itMap = this.topicQueueTable.entrySet().iterator(); + private void cleanTopicByUnRegisterRequests(Set removedBroker, Set reducedBroker) { + Iterator>> itMap = this.topicQueueTable.entrySet().iterator(); while (itMap.hasNext()) { - Entry> entry = itMap.next(); + Entry> entry = itMap.next(); String topic = entry.getKey(); - List queueDataList = entry.getValue(); - Iterator it = queueDataList.iterator(); - while (it.hasNext()) { - QueueData qd = it.next(); - if (qd.getBrokerName().equals(brokerName)) { - log.info("removeTopicByBrokerName, remove one broker's topic {} {}", topic, qd); - it.remove(); + 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 (queueDataList.isEmpty()) { - log.info("removeTopicByBrokerName, remove the topic all queue {}", topic); + 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; - Set brokerNameSet = new HashSet(); - List brokerDataList = new LinkedList(); + List brokerDataList = new LinkedList<>(); topicRouteData.setBrokerDatas(brokerDataList); - HashMap> filterServerMap = new HashMap>(); + HashMap> filterServerMap = new HashMap<>(); topicRouteData.setFilterServerTable(filterServerMap); try { - try { - this.lock.readLock().lockInterruptibly(); - List queueDataList = this.topicQueueTable.get(topic); - if (queueDataList != null) { - topicRouteData.setQueueDatas(queueDataList); - foundQueueData = true; - - Iterator it = queueDataList.iterator(); - while (it.hasNext()) { - QueueData qd = it.next(); - brokerNameSet.add(qd.getBrokerName()); + 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); - for (String brokerName : brokerNameSet) { - BrokerData brokerData = this.brokerAddrTable.get(brokerName); - if (null != brokerData) { - BrokerData brokerDataClone = new BrokerData(brokerData.getCluster(), brokerData.getBrokerName(), (HashMap) brokerData - .getBrokerAddrs().clone()); - brokerDataList.add(brokerDataClone); - foundBrokerData = true; - for (final String brokerAddr : brokerDataClone.getBrokerAddrs().values()) { - List filterServerList = this.filterServerTable.get(brokerAddr); - filterServerMap.put(brokerAddr, filterServerList); - } - } + 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); } + } - } finally { - this.lock.readLock().unlock(); } } 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; } @@ -440,34 +757,63 @@ public TopicRouteData pickupTopicRouteData(final String topic) { } public void scanNotActiveBroker() { - Iterator> it = this.brokerLiveTable.entrySet().iterator(); - while (it.hasNext()) { - Entry next = it.next(); - long last = next.getValue().getLastUpdateTimestamp(); - if ((last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) { - RemotingUtil.closeChannel(next.getValue().getChannel()); - it.remove(); - log.warn("The broker channel expired, {} {}ms", next.getKey(), BROKER_CHANNEL_EXPIRED_TIME); - this.onChannelDestroy(next.getKey(), next.getValue().getChannel()); + 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(String remoteAddr, Channel channel) { - String brokerAddrFound = null; + 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(); - Iterator> itBrokerLiveTable = - this.brokerLiveTable.entrySet().iterator(); - while (itBrokerLiveTable.hasNext()) { - Entry entry = itBrokerLiveTable.next(); + 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(); } @@ -476,104 +822,93 @@ public void onChannelDestroy(String remoteAddr, Channel channel) { } } - if (null == brokerAddrFound) { - brokerAddrFound = remoteAddr; - } else { - log.info("the broker's channel destroyed, {}, clean it's data structure at once", brokerAddrFound); + 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); } + } - if (brokerAddrFound != null && brokerAddrFound.length() > 0) { + private boolean setupUnRegisterRequest(UnRegisterBrokerRequestHeader unRegisterRequest, + BrokerAddrInfo brokerAddrInfo) { + unRegisterRequest.setClusterName(brokerAddrInfo.getClusterName()); + unRegisterRequest.setBrokerAddr(brokerAddrInfo.getBrokerAddr()); - try { - try { - this.lock.writeLock().lockInterruptibly(); - this.brokerLiveTable.remove(brokerAddrFound); - this.filterServerTable.remove(brokerAddrFound); - String brokerNameFound = null; - boolean removeBrokerName = false; - Iterator> itBrokerAddrTable = - this.brokerAddrTable.entrySet().iterator(); - while (itBrokerAddrTable.hasNext() && (null == brokerNameFound)) { - BrokerData brokerData = itBrokerAddrTable.next().getValue(); - - Iterator> it = brokerData.getBrokerAddrs().entrySet().iterator(); - while (it.hasNext()) { - Entry entry = it.next(); - Long brokerId = entry.getKey(); - String brokerAddr = entry.getValue(); - if (brokerAddr.equals(brokerAddrFound)) { - brokerNameFound = brokerData.getBrokerName(); - it.remove(); - log.info("remove brokerAddr[{}, {}] from brokerAddrTable, because channel destroyed", - brokerId, brokerAddr); - break; - } - } + for (Entry stringBrokerDataEntry : this.brokerAddrTable.entrySet()) { + BrokerData brokerData = stringBrokerDataEntry.getValue(); + if (!brokerAddrInfo.getClusterName().equals(brokerData.getCluster())) { + continue; + } - if (brokerData.getBrokerAddrs().isEmpty()) { - removeBrokerName = true; - itBrokerAddrTable.remove(); - log.info("remove brokerName[{}] from brokerAddrTable, because channel destroyed", - brokerData.getBrokerName()); - } - } + 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; + } + } + } - if (brokerNameFound != null && removeBrokerName) { - Iterator>> it = this.clusterAddrTable.entrySet().iterator(); - while (it.hasNext()) { - Entry> entry = it.next(); - String clusterName = entry.getKey(); - Set brokerNames = entry.getValue(); - boolean removed = brokerNames.remove(brokerNameFound); - if (removed) { - log.info("remove brokerName[{}], clusterName[{}] from clusterAddrTable, because channel destroyed", - brokerNameFound, clusterName); - - if (brokerNames.isEmpty()) { - log.info("remove the clusterName[{}] from clusterAddrTable, because channel destroyed and no broker in this cluster", - clusterName); - it.remove(); - } - - break; - } - } - } + return false; + } - if (removeBrokerName) { - Iterator>> itTopicQueueTable = - this.topicQueueTable.entrySet().iterator(); - while (itTopicQueueTable.hasNext()) { - Entry> entry = itTopicQueueTable.next(); - String topic = entry.getKey(); - List queueDataList = entry.getValue(); - - Iterator itQueueData = queueDataList.iterator(); - while (itQueueData.hasNext()) { - QueueData queueData = itQueueData.next(); - if (queueData.getBrokerName().equals(brokerNameFound)) { - itQueueData.remove(); - log.info("remove topic[{} {}], from topicQueueTable, because channel destroyed", - topic, queueData); - } - } + 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()); + } + } + } - if (queueDataList.isEmpty()) { - itTopicQueueTable.remove(); - log.info("remove topic[{}] all queue, from topicQueueTable, because channel destroyed", - topic); - } - } - } - } finally { - this.lock.writeLock().unlock(); - } - } catch (Exception e) { - log.error("onChannelDestroy Exception", e); + 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 { @@ -581,36 +916,28 @@ public void printAllPeriodically() { log.info("--------------------------------------------------------"); { log.info("topicQueueTable SIZE: {}", this.topicQueueTable.size()); - Iterator>> it = this.topicQueueTable.entrySet().iterator(); - while (it.hasNext()) { - Entry> next = it.next(); + for (Entry> next : this.topicQueueTable.entrySet()) { log.info("topicQueueTable Topic: {} {}", next.getKey(), next.getValue()); } } { log.info("brokerAddrTable SIZE: {}", this.brokerAddrTable.size()); - Iterator> it = this.brokerAddrTable.entrySet().iterator(); - while (it.hasNext()) { - Entry next = it.next(); + for (Entry next : this.brokerAddrTable.entrySet()) { log.info("brokerAddrTable brokerName: {} {}", next.getKey(), next.getValue()); } } { log.info("brokerLiveTable SIZE: {}", this.brokerLiveTable.size()); - Iterator> it = this.brokerLiveTable.entrySet().iterator(); - while (it.hasNext()) { - Entry next = it.next(); + for (Entry next : this.brokerLiveTable.entrySet()) { log.info("brokerLiveTable brokerAddr: {} {}", next.getKey(), next.getValue()); } } { log.info("clusterAddrTable SIZE: {}", this.clusterAddrTable.size()); - Iterator>> it = this.clusterAddrTable.entrySet().iterator(); - while (it.hasNext()) { - Entry> next = it.next(); + for (Entry> next : this.clusterAddrTable.entrySet()) { log.info("clusterAddrTable clusterName: {} {}", next.getKey(), next.getValue()); } } @@ -622,56 +949,48 @@ public void printAllPeriodically() { } } - public byte[] getSystemTopicList() { + public TopicList getSystemTopicList() { TopicList topicList = new TopicList(); try { - try { - this.lock.readLock().lockInterruptibly(); - for (Map.Entry> entry : clusterAddrTable.entrySet()) { - topicList.getTopicList().add(entry.getKey()); - topicList.getTopicList().addAll(entry.getValue()); - } + this.lock.readLock().lockInterruptibly(); + for (Map.Entry> entry : clusterAddrTable.entrySet()) { + topicList.getTopicList().add(entry.getKey()); + topicList.getTopicList().addAll(entry.getValue()); + } - if (brokerAddrTable != null && !brokerAddrTable.isEmpty()) { - Iterator it = brokerAddrTable.keySet().iterator(); - while (it.hasNext()) { - BrokerData bd = brokerAddrTable.get(it.next()); - HashMap brokerAddrs = bd.getBrokerAddrs(); - if (brokerAddrs != null && !brokerAddrs.isEmpty()) { - Iterator it2 = brokerAddrs.keySet().iterator(); - topicList.setBrokerAddr(brokerAddrs.get(it2.next())); - break; - } + 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; } } - } finally { - this.lock.readLock().unlock(); } } catch (Exception e) { - log.error("getAllTopicList Exception", e); + log.error("getSystemTopicList Exception", e); + } finally { + this.lock.readLock().unlock(); } - return topicList.encode(); + return topicList; } - public byte[] getTopicsByCluster(String cluster) { + 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) { - Iterator>> topicTableIt = - this.topicQueueTable.entrySet().iterator(); - while (topicTableIt.hasNext()) { - Entry> topicEntry = topicTableIt.next(); + for (Entry> topicEntry : this.topicQueueTable.entrySet()) { String topic = topicEntry.getKey(); - List queueDatas = topicEntry.getValue(); - for (QueueData queueData : queueDatas) { - if (brokerName.equals(queueData.getBrokerName())) { - topicList.getTopicList().add(topic); - break; - } + Map queueDataMap = topicEntry.getValue(); + final QueueData qd = queueDataMap.get(brokerName); + if (qd != null) { + topicList.getTopicList().add(topic); } } } @@ -679,101 +998,153 @@ public byte[] getTopicsByCluster(String cluster) { this.lock.readLock().unlock(); } } catch (Exception e) { - log.error("getAllTopicList Exception", e); + log.error("getTopicsByCluster Exception", e); } - return topicList.encode(); + return topicList; } - public byte[] getUnitTopics() { + public TopicList getUnitTopics() { TopicList topicList = new TopicList(); try { - try { - this.lock.readLock().lockInterruptibly(); - Iterator>> topicTableIt = - this.topicQueueTable.entrySet().iterator(); - while (topicTableIt.hasNext()) { - Entry> topicEntry = topicTableIt.next(); - String topic = topicEntry.getKey(); - List queueDatas = topicEntry.getValue(); - if (queueDatas != null && queueDatas.size() > 0 - && TopicSysFlag.hasUnitFlag(queueDatas.get(0).getTopicSysFlag())) { - topicList.getTopicList().add(topic); - } + 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); } - } finally { - this.lock.readLock().unlock(); } } catch (Exception e) { - log.error("getAllTopicList Exception", e); + log.error("getUnitTopics Exception", e); + } finally { + this.lock.readLock().unlock(); } - return topicList.encode(); + return topicList; } - public byte[] getHasUnitSubTopicList() { + public TopicList getHasUnitSubTopicList() { TopicList topicList = new TopicList(); try { - try { - this.lock.readLock().lockInterruptibly(); - Iterator>> topicTableIt = - this.topicQueueTable.entrySet().iterator(); - while (topicTableIt.hasNext()) { - Entry> topicEntry = topicTableIt.next(); - String topic = topicEntry.getKey(); - List queueDatas = topicEntry.getValue(); - if (queueDatas != null && queueDatas.size() > 0 - && TopicSysFlag.hasUnitSubFlag(queueDatas.get(0).getTopicSysFlag())) { - topicList.getTopicList().add(topic); - } + 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); } - } finally { - this.lock.readLock().unlock(); } } catch (Exception e) { - log.error("getAllTopicList Exception", e); + log.error("getHasUnitSubTopicList Exception", e); + } finally { + this.lock.readLock().unlock(); } - return topicList.encode(); + return topicList; } - public byte[] getHasUnitSubUnUnitTopicList() { + public TopicList getHasUnitSubUnUnitTopicList() { TopicList topicList = new TopicList(); try { - try { - this.lock.readLock().lockInterruptibly(); - Iterator>> topicTableIt = - this.topicQueueTable.entrySet().iterator(); - while (topicTableIt.hasNext()) { - Entry> topicEntry = topicTableIt.next(); - String topic = topicEntry.getKey(); - List queueDatas = topicEntry.getValue(); - if (queueDatas != null && queueDatas.size() > 0 - && !TopicSysFlag.hasUnitFlag(queueDatas.get(0).getTopicSysFlag()) - && TopicSysFlag.hasUnitSubFlag(queueDatas.get(0).getTopicSysFlag())) { - topicList.getTopicList().add(topic); - } + 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); } - } finally { - this.lock.readLock().unlock(); } } catch (Exception e) { - log.error("getAllTopicList Exception", e); + log.error("getHasUnitSubUnUnitTopicList Exception", e); + } finally { + this.lock.readLock().unlock(); } - return topicList.encode(); + 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, DataVersion dataVersion, Channel channel, + 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; @@ -787,6 +1158,14 @@ 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; } @@ -817,3 +1196,39 @@ public String toString() { + ", 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; + } +} diff --git a/distribution/conf/logback_namesrv.xml b/namesrv/src/main/resources/rmq.namesrv.logback.xml similarity index 52% rename from distribution/conf/logback_namesrv.xml rename to namesrv/src/main/resources/rmq.namesrv.logback.xml index 36eda6801b4..2a3c95722a5 100644 --- a/distribution/conf/logback_namesrv.xml +++ b/namesrv/src/main/resources/rmq.namesrv.logback.xml @@ -16,18 +16,18 @@ limitations under the License. --> - + - ${user.home}/logs/rocketmqlogs/namesrv_default.log + ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}namesrv_default.log true - ${user.home}/logs/rocketmqlogs/otherdays/namesrv_default.%i.log.gz + ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}namesrv_default.%i.log.gz 1 5 + class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> 100MB @@ -38,15 +38,15 @@ - ${user.home}/logs/rocketmqlogs/namesrv.log + ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}namesrv.log true - ${user.home}/logs/rocketmqlogs/otherdays/namesrv.%i.log.gz + ${user.home}${file.separator}logs${file.separator}rocketmqlogs${file.separator}otherdays${file.separator}namesrv.%i.log.gz 1 5 + class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> 100MB @@ -59,36 +59,59 @@ 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,SSS} %p %t - %m%n + %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 + + + + - - + - - + - - + - - + + + + + + + + + diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/NameServerInstanceTest.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/NameServerInstanceTest.java index 83ab103f66d..7768272f312 100644 --- a/namesrv/src/test/java/org/apache/rocketmq/namesrv/NameServerInstanceTest.java +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/NameServerInstanceTest.java @@ -20,6 +20,7 @@ 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; @@ -37,6 +38,14 @@ public void startup() throws Exception { nameSrvController.start(); } + /** + * Add a placeholder to make Bazel happy. + */ + @Test + public void itWorks() { + + } + @After public void shutdown() throws Exception { if (nameSrvController != null) { diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/NamesrvControllerTest.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/NamesrvControllerTest.java new file mode 100644 index 00000000000..49d7103aa3d --- /dev/null +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/NamesrvControllerTest.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/NamesrvStartupTest.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/NamesrvStartupTest.java new file mode 100644 index 00000000000..957406dac06 --- /dev/null +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/NamesrvStartupTest.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} \ No newline at end of file diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/ClusterTestRequestProcessorTest.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/ClusterTestRequestProcessorTest.java index c8bf057606d..283f9033021 100644 --- a/namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/ClusterTestRequestProcessorTest.java +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/ClusterTestRequestProcessorTest.java @@ -22,21 +22,25 @@ 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.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; 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; @@ -44,6 +48,7 @@ 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; @@ -52,16 +57,17 @@ public class ClusterTestRequestProcessorTest { private ClusterTestRequestProcessor clusterTestProcessor; private DefaultMQAdminExtImpl defaultMQAdminExtImpl; - private MQClientInstance mqClientInstance = MQClientManager.getInstance().getOrCreateMQClientInstance(new ClientConfig()); + private MQClientInstance mqClientInstance = MQClientManager.getInstance() + .getOrCreateMQClientInstance(new ClientConfig()); private MQClientAPIImpl mQClientAPIImpl; private ChannelHandlerContext ctx; @Before - public void init() throws NoSuchFieldException, IllegalAccessException, RemotingException, MQClientException, InterruptedException { + public void init() throws NoSuchFieldException, IllegalAccessException, RemotingException, MQClientException, + InterruptedException { NamesrvController namesrvController = new NamesrvController( - new NamesrvConfig(), - new NettyServerConfig() - ); + new NamesrvConfig(), + new NettyServerConfig()); clusterTestProcessor = new ClusterTestRequestProcessor(namesrvController, "default-producer"); mQClientAPIImpl = mock(MQClientAPIImpl.class); @@ -82,7 +88,7 @@ public void init() throws NoSuchFieldException, IllegalAccessException, Remoting TopicRouteData topicRouteData = new TopicRouteData(); List brokerDatas = new ArrayList<>(); HashMap brokerAddrs = new HashMap<>(); - brokerAddrs.put(1234l, "127.0.0.1:10911"); + brokerAddrs.put(1234L, "127.0.0.1:10911"); BrokerData brokerData = new BrokerData(); brokerData.setCluster("default-cluster"); brokerData.setBrokerName("default-broker"); @@ -110,4 +116,94 @@ public void checkFields() throws RemotingCommandException { assertThat(remoting.getRemark()).isNotNull(); } -} \ No newline at end of file + @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; + } + +} diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessorTest.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/RequestProcessorTest.java similarity index 59% rename from namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessorTest.java rename to namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/RequestProcessorTest.java index 22cf978fe49..5bdf96d9de7 100644 --- a/namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessorTest.java +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/RequestProcessorTest.java @@ -20,28 +20,35 @@ 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.logging.InternalLogger; +import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.namesrv.NamesrvConfig; -import org.apache.rocketmq.common.namesrv.RegisterBrokerResult; -import org.apache.rocketmq.common.protocol.RequestCode; -import org.apache.rocketmq.common.protocol.ResponseCode; -import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper; -import org.apache.rocketmq.common.protocol.header.namesrv.DeleteKVConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.GetKVConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.GetKVConfigResponseHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.PutKVConfigRequestHeader; -import org.apache.rocketmq.common.protocol.header.namesrv.RegisterBrokerRequestHeader; -import org.apache.rocketmq.common.protocol.route.BrokerData; +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; @@ -50,9 +57,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class DefaultRequestProcessorTest { +public class RequestProcessorTest { private DefaultRequestProcessor defaultRequestProcessor; + private ClientRequestProcessor clientRequestProcessor; + private NamesrvController namesrvController; private NamesrvConfig namesrvConfig; @@ -61,13 +70,14 @@ public class DefaultRequestProcessorTest { private RouteInfoManager routeInfoManager; - private InternalLogger logger; + private Logger logger; @Before public void init() throws Exception { namesrvConfig = new NamesrvConfig(); + namesrvConfig.setEnableAllTopicList(true); nettyServerConfig = new NettyServerConfig(); - routeInfoManager = new RouteInfoManager(); + routeInfoManager = new RouteInfoManager(new NamesrvConfig(), null); namesrvController = new NamesrvController(namesrvConfig, nettyServerConfig); @@ -76,9 +86,11 @@ public void init() throws Exception { field.set(namesrvController, routeInfoManager); defaultRequestProcessor = new DefaultRequestProcessor(namesrvController); + clientRequestProcessor = new ClientRequestProcessor(namesrvController); + registerRouteInfoManager(); - logger = mock(InternalLogger.class); + logger = mock(Logger.class); setFinalStatic(DefaultRequestProcessor.class.getDeclaredField("log"), logger); } @@ -159,6 +171,52 @@ public void testProcessRequest_DeleteKVConfig() throws RemotingCommandException .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 path"); + + //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 path"); + } + @Test public void testProcessRequest_RegisterBroker() throws RemotingCommandException, NoSuchFieldException, IllegalAccessException { @@ -184,6 +242,106 @@ public void testProcessRequest_RegisterBroker() throws RemotingCommandException, .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 { @@ -236,14 +394,37 @@ public void testProcessRequest_UnregisterBroker() throws RemotingCommandExceptio } @Test - public void testGetRouteInfoByTopic() throws RemotingCommandException { + 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 = defaultRequestProcessor.processRequest(ctx, request); + RemotingCommand remotingCommandSuccess = clientRequestProcessor.processRequest(ctx, request); assertThat(remotingCommandSuccess.getCode()).isEqualTo(ResponseCode.SUCCESS); request.getExtFields().put("topic", "test"); - RemotingCommand remotingCommandNoTopicRouteInfo = defaultRequestProcessor.processRequest(ctx, request); + RemotingCommand remotingCommandNoTopicRouteInfo = clientRequestProcessor.processRequest(ctx, request); assertThat(remotingCommandNoTopicRouteInfo.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST); } @@ -268,7 +449,8 @@ public void testWipeWritePermOfBroker() throws RemotingCommandException { @Test public void testGetAllTopicListFromNameserver() throws RemotingCommandException { ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); - when(ctx.channel()).thenReturn(null); + 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); @@ -378,12 +560,23 @@ private RemotingCommand getRemotingCommand(int code) { request.addExtField("clusterName", "cluster"); request.addExtField("haServerAddr", "10.10.2.1"); request.addExtField("brokerId", "2333"); - request.addExtField("topic", "unit-test"); + 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); @@ -392,6 +585,7 @@ private static RemotingCommand genSampleRegisterCmd(boolean reg) { request.addExtField("clusterName", "cluster"); request.addExtField("haServerAddr", "10.10.2.1"); request.addExtField("brokerId", "2333"); + request.setBody(body); return request; } @@ -406,17 +600,20 @@ private static void setFinalStatic(Field field, Object newValue) throws Exceptio private void registerRouteInfoManager() { TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); ConcurrentHashMap topicConfigConcurrentHashMap = new ConcurrentHashMap<>(); - TopicConfig topicConfig = new TopicConfig(); - topicConfig.setWriteQueueNums(8); - topicConfig.setTopicName("unit-test"); - topicConfig.setPerm(6); - topicConfig.setReadQueueNums(8); - topicConfig.setOrder(false); - topicConfigConcurrentHashMap.put("unit-test", topicConfig); + 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", 0, "127.0.0.1:1001", - topicConfigSerializeWrapper, new ArrayList(), channel); + RegisterBrokerResult registerBrokerResult = routeInfoManager.registerBroker("default-cluster", "127.0.0.1:10911", "default-broker", 1234, "127.0.0.1:1001", "", + null, topicConfigSerializeWrapper, new ArrayList<>(), channel); } -} \ No newline at end of file + +} diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/GetRouteInfoBenchmark.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/GetRouteInfoBenchmark.java new file mode 100644 index 00000000000..b453f4ded74 --- /dev/null +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/GetRouteInfoBenchmark.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RegisterBrokerBenchmark.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RegisterBrokerBenchmark.java new file mode 100644 index 00000000000..0e9cf67f565 --- /dev/null +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RegisterBrokerBenchmark.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerBrokerPermTest.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerBrokerPermTest.java new file mode 100644 index 00000000000..5a2fab8f0eb --- /dev/null +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerBrokerPermTest.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + + } +} diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerBrokerRegisterTest.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerBrokerRegisterTest.java new file mode 100644 index 00000000000..aa616e64da9 --- /dev/null +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerBrokerRegisterTest.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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)); + } +} diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerNewTest.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerNewTest.java new file mode 100644 index 00000000000..b53519e5f64 --- /dev/null +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerNewTest.java @@ -0,0 +1,788 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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() { + registerBrokerWithNormalTopic(BrokerBasicInfo.defaultBroker(), "TestTopic"); + 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 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); + } + + 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 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); + + RegisterBrokerResult registerBrokerResult = routeInfoManager.registerBroker( + brokerInfo.clusterName, + brokerInfo.brokerAddr, + brokerInfo.brokerName, + brokerInfo.brokerId, + brokerInfo.haAddr, + "", + null, + brokerInfo.enableActingMaster, + topicConfigSerializeWrapper, new ArrayList<>(), channel); + return registerBrokerResult; + } + + 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; + } + } + +} diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerStaticRegisterTest.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerStaticRegisterTest.java new file mode 100644 index 00000000000..6c90e7b710b --- /dev/null +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerStaticRegisterTest.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } + +} diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerTest.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerTest.java index e0d9e1871c0..d9ac9e49461 100644 --- a/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerTest.java +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerTest.java @@ -17,23 +17,25 @@ 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.RegisterBrokerResult; -import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; +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 java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -43,56 +45,104 @@ public class RouteInfoManagerTest { @Before public void setup() { - routeInfoManager = new RouteInfoManager(); + 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(); + 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(); + byte[] topicInfo = routeInfoManager.getAllTopicList().encode(); Assert.assertTrue(topicInfo != null); assertThat(topicInfo).isNotNull(); } @Test public void testRegisterBroker() { - TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); + DataVersion dataVersion = new DataVersion(); + dataVersion.setCounter(new AtomicLong(10L)); + dataVersion.setTimestamp(100L); + ConcurrentHashMap topicConfigConcurrentHashMap = new ConcurrentHashMap<>(); - TopicConfig topicConfig = new TopicConfig(); - topicConfig.setWriteQueueNums(8); - topicConfig.setTopicName("unit-test"); - topicConfig.setPerm(6); - topicConfig.setReadQueueNums(8); - topicConfig.setOrder(false); - topicConfigConcurrentHashMap.put("unit-test", topicConfig); + 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", - topicConfigSerializeWrapper, new ArrayList(), channel); + 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 { - List qdList = new ArrayList<>(); + Map qdMap = new HashMap<>(); + QueueData qd = new QueueData(); qd.setPerm(PermName.PERM_READ | PermName.PERM_WRITE); qd.setBrokerName("broker-a"); - qdList.add(qd); - HashMap> topicQueueTable = new HashMap<>(); - topicQueueTable.put("topic-a", qdList); + qdMap.put("broker-a",qd); + HashMap> topicQueueTable = new HashMap<>(); + topicQueueTable.put("topic-a", qdMap); Field filed = RouteInfoManager.class.getDeclaredField("topicQueueTable"); filed.setAccessible(true); @@ -112,43 +162,43 @@ public void testPickupTopicRouteData() { @Test public void testGetSystemTopicList() { - byte[] topicList = routeInfoManager.getSystemTopicList(); + byte[] topicList = routeInfoManager.getSystemTopicList().encode(); assertThat(topicList).isNotNull(); } @Test public void testGetTopicsByCluster() { - byte[] topicList = routeInfoManager.getTopicsByCluster("default-cluster"); + byte[] topicList = routeInfoManager.getTopicsByCluster("default-cluster").encode(); assertThat(topicList).isNotNull(); } @Test public void testGetUnitTopics() { - byte[] topicList = routeInfoManager.getUnitTopics(); + byte[] topicList = routeInfoManager.getUnitTopics().encode(); assertThat(topicList).isNotNull(); } @Test public void testGetHasUnitSubTopicList() { - byte[] topicList = routeInfoManager.getHasUnitSubTopicList(); + byte[] topicList = routeInfoManager.getHasUnitSubTopicList().encode(); assertThat(topicList).isNotNull(); } @Test public void testGetHasUnitSubUnUnitTopicList() { - byte[] topicList = routeInfoManager.getHasUnitSubUnUnitTopicList(); + byte[] topicList = routeInfoManager.getHasUnitSubUnUnitTopicList().encode(); assertThat(topicList).isNotNull(); } @Test public void testAddWritePermOfBrokerByLock() throws Exception { - List qdList = new ArrayList<>(); + Map qdMap = new HashMap<>(); QueueData qd = new QueueData(); qd.setPerm(PermName.PERM_READ); qd.setBrokerName("broker-a"); - qdList.add(qd); - HashMap> topicQueueTable = new HashMap<>(); - topicQueueTable.put("topic-a", qdList); + qdMap.put("broker-a",qd); + HashMap> topicQueueTable = new HashMap<>(); + topicQueueTable.put("topic-a", qdMap); Field filed = RouteInfoManager.class.getDeclaredField("topicQueueTable"); filed.setAccessible(true); @@ -159,4 +209,4 @@ public void testAddWritePermOfBrokerByLock() throws Exception { assertThat(qd.getPerm()).isEqualTo(PermName.PERM_READ | PermName.PERM_WRITE); } -} \ No newline at end of file +} diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerTestBase.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerTestBase.java new file mode 100644 index 00000000000..a5faeea9dda --- /dev/null +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManagerTestBase.java @@ -0,0 +1,189 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + +} diff --git a/namesrv/src/test/resources/rmq.logback-test.xml b/namesrv/src/test/resources/rmq.logback-test.xml new file mode 100644 index 00000000000..8695d52d57c --- /dev/null +++ b/namesrv/src/test/resources/rmq.logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/openmessaging/pom.xml b/openmessaging/pom.xml index 23d28713df7..28840883915 100644 --- a/openmessaging/pom.xml +++ b/openmessaging/pom.xml @@ -20,13 +20,17 @@ rocketmq-all org.apache.rocketmq - 4.9.4-SNAPSHOT + 5.1.3 4.0.0 rocketmq-openmessaging rocketmq-openmessaging ${project.version} + + ${basedir}/.. + + io.openmessaging @@ -35,6 +39,7 @@ ${project.groupId} rocketmq-client + ${project.version} \ No newline at end of file diff --git a/openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/LocalMessageCache.java b/openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/LocalMessageCache.java index d2fc6781654..3b2d0141c0b 100644 --- a/openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/LocalMessageCache.java +++ b/openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/LocalMessageCache.java @@ -35,15 +35,17 @@ 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.client.log.ClientLogger; 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.InternalLogger; +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; @@ -51,8 +53,6 @@ class LocalMessageCache implements ServiceLifecycle { private final ClientConfig clientConfig; private final ScheduledExecutorService cleanExpireMsgExecutors; - private final static InternalLogger log = ClientLogger.getLog(); - LocalMessageCache(final DefaultMQPullConsumer rocketmqPullConsumer, final ClientConfig clientConfig) { consumeRequestCache = new LinkedBlockingQueue<>(clientConfig.getRmqPullMessageCacheCapacity()); this.consumedRequest = new ConcurrentHashMap<>(); diff --git a/openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/PullConsumerImpl.java b/openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/PullConsumerImpl.java index 945ecfac855..670d1abaac4 100644 --- a/openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/PullConsumerImpl.java +++ b/openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/PullConsumerImpl.java @@ -33,13 +33,15 @@ 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.client.log.ClientLogger; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.logging.InternalLogger; 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; @@ -47,8 +49,6 @@ public class PullConsumerImpl implements PullConsumer { private final LocalMessageCache localMessageCache; private final ClientConfig clientConfig; - private final static InternalLogger log = ClientLogger.getLog(); - public PullConsumerImpl(final KeyValue properties) { this.properties = properties; this.clientConfig = BeanUtils.populate(properties, ClientConfig.class); diff --git a/openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/PushConsumerImpl.java b/openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/PushConsumerImpl.java index d5d394a6994..1675a16f1ba 100644 --- a/openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/PushConsumerImpl.java +++ b/openmessaging/src/main/java/io/openmessaging/rocketmq/consumer/PushConsumerImpl.java @@ -101,7 +101,7 @@ public void suspend(long timeout) { @Override public boolean isSuspended() { - return this.rocketmqPushConsumer.getDefaultMQPushConsumerImpl().isPause(); + return this.rocketmqPushConsumer.isPause(); } @Override diff --git a/openmessaging/src/main/java/io/openmessaging/rocketmq/producer/AbstractOMSProducer.java b/openmessaging/src/main/java/io/openmessaging/rocketmq/producer/AbstractOMSProducer.java index 3db859048f6..e03246142c9 100644 --- a/openmessaging/src/main/java/io/openmessaging/rocketmq/producer/AbstractOMSProducer.java +++ b/openmessaging/src/main/java/io/openmessaging/rocketmq/producer/AbstractOMSProducer.java @@ -31,18 +31,15 @@ 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.log.ClientLogger; import org.apache.rocketmq.client.producer.DefaultMQProducer; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.common.protocol.ResponseCode; 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 static InternalLogger log = ClientLogger.getLog(); final KeyValue properties; final DefaultMQProducer rocketmqProducer; private boolean started = false; diff --git a/openmessaging/src/main/java/io/openmessaging/rocketmq/producer/ProducerImpl.java b/openmessaging/src/main/java/io/openmessaging/rocketmq/producer/ProducerImpl.java index c2b6d3e3c77..af712cac31f 100644 --- a/openmessaging/src/main/java/io/openmessaging/rocketmq/producer/ProducerImpl.java +++ b/openmessaging/src/main/java/io/openmessaging/rocketmq/producer/ProducerImpl.java @@ -30,11 +30,15 @@ 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); } diff --git a/openmessaging/src/main/java/io/openmessaging/rocketmq/promise/DefaultPromise.java b/openmessaging/src/main/java/io/openmessaging/rocketmq/promise/DefaultPromise.java index c1b59993f6f..36ac27f417a 100644 --- a/openmessaging/src/main/java/io/openmessaging/rocketmq/promise/DefaultPromise.java +++ b/openmessaging/src/main/java/io/openmessaging/rocketmq/promise/DefaultPromise.java @@ -19,14 +19,14 @@ import io.openmessaging.Promise; import io.openmessaging.FutureListener; import io.openmessaging.exception.OMSRuntimeException; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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 InternalLogger LOG = InternalLoggerFactory.getLogger(DefaultPromise.class); + 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; @@ -157,7 +157,7 @@ private void notifyListeners() { } private boolean isSuccess() { - return isDone() && (exception == null); + return isDone() && exception == null; } private void timeoutSoCancel() { diff --git a/openmessaging/src/main/java/io/openmessaging/rocketmq/utils/BeanUtils.java b/openmessaging/src/main/java/io/openmessaging/rocketmq/utils/BeanUtils.java index 9853da815c5..de91374c052 100644 --- a/openmessaging/src/main/java/io/openmessaging/rocketmq/utils/BeanUtils.java +++ b/openmessaging/src/main/java/io/openmessaging/rocketmq/utils/BeanUtils.java @@ -25,16 +25,16 @@ import java.util.Properties; import java.util.Set; import org.apache.commons.lang3.StringUtils; -import org.apache.rocketmq.client.log.ClientLogger; -import org.apache.rocketmq.logging.InternalLogger; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public final class BeanUtils { - final static InternalLogger log = ClientLogger.getLog(); + 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, Class>(); + private static Map, Class> primitiveWrapperMap = new HashMap<>(); static { primitiveWrapperMap.put(Boolean.TYPE, Boolean.class); @@ -48,7 +48,7 @@ public final class BeanUtils { primitiveWrapperMap.put(Void.TYPE, Void.TYPE); } - private static Map, Class> wrapperMap = new HashMap, Class>(); + private static Map, Class> wrapperMap = new HashMap<>(); static { for (Entry, Class> primitiveClass : primitiveWrapperMap.entrySet()) { @@ -87,7 +87,7 @@ public final class BeanUtils { public static T populate(final Properties properties, final Class clazz) { T obj = null; try { - obj = clazz.newInstance(); + obj = clazz.getDeclaredConstructor().newInstance(); return populate(properties, obj); } catch (Throwable e) { log.warn("Error occurs !", e); @@ -98,7 +98,7 @@ public static T populate(final Properties properties, final Class clazz) public static T populate(final KeyValue properties, final Class clazz) { T obj = null; try { - obj = clazz.newInstance(); + obj = clazz.getDeclaredConstructor().newInstance(); return populate(properties, obj); } catch (Throwable e) { log.warn("Error occurs !", e); diff --git a/openmessaging/src/test/resources/rmq.logback-test.xml b/openmessaging/src/test/resources/rmq.logback-test.xml new file mode 100644 index 00000000000..8695d52d57c --- /dev/null +++ b/openmessaging/src/test/resources/rmq.logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index ec051303dd2..48e78460323 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - @@ -29,7 +28,7 @@ 2012 org.apache.rocketmq rocketmq-all - 4.9.4-SNAPSHOT + 5.1.3 pom Apache RocketMQ ${project.version} http://rocketmq.apache.org/ @@ -38,7 +37,7 @@ git@github.com:apache/rocketmq.git scm:git:git@github.com:apache/rocketmq.git scm:git:git@github.com:apache/rocketmq.git - HEAD + rocketmq-all-5.1.3 @@ -91,6 +90,7 @@ UTF-8 UTF-8 + ${basedir} false @@ -98,11 +98,81 @@ 1.8 1.8 + + 1.5.0 + 4.1.65.Final + 2.0.53.Final + 1.69 + 1.2.83 + 3.20.0-GA + 4.2.2 + 3.12.0 + 2.7 + 31.1-jre + 2.9.0 + 0.3.1-alpha + 1.32 + 1.13 + 1.0.1 + 2.0.3 + 1.0.0 + 1.7 + 1.5.2-2 + 1.8.0 + 0.33.0 + 1.6.0 + 0.3.1.2 + 6.0.53 + 1.0-beta-4 + 1.4.2 + 2.0.2 + 1.50.0 + 3.20.1 + 1.2.10 + 0.9.11 + 2.9.3 + 5.3.27 + 3.0.0 + 1.26.0 + 1.26.0-alpha + 2.0.6 + 2.20.29 + 2.13.4.2 + + + 4.13.2 + 3.22.0 + 3.10.0 + 2.0.9 + 4.1.0 + 0.30 + 2.11.0 + + + 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/** - @@ -118,9 +188,12 @@ test distribution openmessaging - logging acl example + container + controller + proxy + tieredstore @@ -128,21 +201,21 @@ org.codehaus.mojo versions-maven-plugin - 2.2 + ${versions-maven-plugin.version} com.github.vongosling dependency-mediator-maven-plugin - 1.0.2 + ${dependency-mediator-maven-plugin.version} org.codehaus.mojo clirr-maven-plugin - 2.7 + ${clirr-maven-plugin.version} maven-enforcer-plugin - 1.4.1 + ${maven-enforcer-plugin.version} enforce-ban-circular-dependencies @@ -154,6 +227,7 @@ + true @@ -161,13 +235,13 @@ org.codehaus.mojo extra-enforcer-rules - 1.0-beta-4 + ${extra-enforcer-rules.version} maven-compiler-plugin - 3.5.1 + ${maven-compiler-plugin.version} ${maven.compiler.source} ${maven.compiler.target} @@ -178,7 +252,7 @@ maven-source-plugin - 3.0.1 + ${maven-source-plugin.version} attach-sources @@ -190,7 +264,7 @@ maven-help-plugin - 2.2 + ${maven-help-plugin.version} generate-effective-dependencies-pom @@ -203,17 +277,18 @@ maven-checkstyle-plugin - 2.17 + ${maven-checkstyle-plugin.version} - verify - verify + validate + validate style/rmq_checkstyle.xml - UTF-8 + UTF-8 true true - false + true + **/generated*/**/* check @@ -224,14 +299,16 @@ org.apache.rat apache-rat-plugin - 0.12 + ${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/service/* @@ -240,12 +317,13 @@ */*.iml docs/** localbin/** + conf/rmq-proxy.json maven-resources-plugin - 3.0.2 + ${maven-resources-plugin.version} ${project.build.sourceEncoding} @@ -254,12 +332,12 @@ org.eluder.coveralls coveralls-maven-plugin - 4.3.0 + ${coveralls-maven-plugin.version} org.jacoco jacoco-maven-plugin - 0.8.5 + ${jacoco-maven-plugin.version} default-prepare-agent @@ -270,6 +348,13 @@ ${project.build.directory}/jacoco.exec + + report + test + + report + + default-prepare-agent-integration pre-integration-test @@ -297,7 +382,7 @@ maven-surefire-plugin - 2.19.1 + ${maven-surefire-plugin.version} 1 1 @@ -307,15 +392,32 @@ - - org.codehaus.mojo - findbugs-maven-plugin - 3.0.4 - org.sonarsource.scanner.maven sonar-maven-plugin - 3.0.2 + ${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 + @@ -323,7 +425,7 @@ maven-assembly-plugin - 3.0.0 + ${maven-assembly-plugin.version} @@ -340,7 +442,7 @@ maven-javadoc-plugin - 2.10.4 + ${maven-javadoc-plugin.version} -Xdoclint:none @@ -351,7 +453,7 @@ maven-javadoc-plugin - 2.10.4 + ${maven-javadoc-plugin.version} -Xdoclint:none @@ -391,7 +493,7 @@ maven-failsafe-plugin - 2.19.1 + ${maven-failsafe-plugin.version} @{failsafeArgLine} @@ -419,194 +521,558 @@ - - - junit - junit - 4.13.2 - test - - - org.assertj - assertj-core - 2.6.0 - test - - - org.mockito - mockito-core - 3.10.0 - test - - - org.awaitility - awaitility - 4.1.0 - test - - - - ${project.groupId} - rocketmq-client + org.apache.rocketmq + rocketmq-acl ${project.version} - ${project.groupId} + org.apache.rocketmq rocketmq-broker ${project.version} - ${project.groupId} + org.apache.rocketmq + rocketmq-client + ${project.version} + + + org.apache.rocketmq rocketmq-common ${project.version} - ${project.groupId} - rocketmq-store + org.apache.rocketmq + rocketmq-container ${project.version} - ${project.groupId} - rocketmq-namesrv + org.apache.rocketmq + rocketmq-controller ${project.version} - ${project.groupId} - rocketmq-tools + org.apache.rocketmq + rocketmq-example ${project.version} - ${project.groupId} - rocketmq-remoting + org.apache.rocketmq + rocketmq-filter ${project.version} - ${project.groupId} - rocketmq-logging + org.apache.rocketmq + rocketmq-namesrv ${project.version} - ${project.groupId} - rocketmq-test + org.apache.rocketmq + rocketmq-openmessaging ${project.version} - ${project.groupId} - rocketmq-srvutil + org.apache.rocketmq + rocketmq-proxy ${project.version} org.apache.rocketmq - rocketmq-filter + rocketmq-remoting ${project.version} - ${project.groupId} - rocketmq-acl + org.apache.rocketmq + rocketmq-srvutil ${project.version} - ${project.groupId} - rocketmq-openmessaging + org.apache.rocketmq + rocketmq-store ${project.version} - ${project.groupId} - rocketmq-example + org.apache.rocketmq + rocketmq-tiered-store ${project.version} - org.slf4j - slf4j-api - 1.7.7 + org.apache.rocketmq + rocketmq-test + ${project.version} - ch.qos.logback - logback-classic - 1.2.10 + org.apache.rocketmq + rocketmq-tools + ${project.version} + + + ${project.groupId} + rocketmq-proto + ${rocketmq-proto.version} + + + io.grpc + grpc-protobuf + + + io.grpc + grpc-stub + + + io.grpc + grpc-netty-shaded + + commons-cli commons-cli - 1.2 + ${commons-cli.version} io.netty netty-all - 4.1.65.Final + ${netty.version} org.bouncycastle bcpkix-jdk15on runtime jar - 1.69 + ${bcpkix-jdk15on.version} com.alibaba fastjson - 1.2.76 + ${fastjson.version} org.javassist javassist - 3.20.0-GA + ${javassist.version} net.java.dev.jna jna - 4.2.2 + ${jna.version} org.apache.commons commons-lang3 - 3.4 + ${commons-lang3.version} + + + commons-io + commons-io + ${commons-io.version} com.google.guava guava - 31.0.1-jre + ${guava.version} + + + com.google.errorprone + error_prone_annotations + + - io.openmessaging - openmessaging-api - 0.3.1-alpha + com.google.code + gson + ${gson.version} - log4j - log4j - 1.2.17 + com.googlecode.concurrentlinkedhashmap + concurrentlinkedhashmap-lru + ${concurrentlinkedhashmap-lru.version} + + + io.openmessaging + openmessaging-api + ${openmessaging.version} org.yaml snakeyaml - 1.30 + ${snakeyaml.version} commons-codec commons-codec - 1.9 + ${commons-codec.version} + + + org.slf4j + slf4j-api + ${slf4j-api.version} - org.apache.logging.log4j - log4j-core - 2.17.1 + io.github.aliyunmq + rocketmq-shaded-slf4j-api-bridge + ${rocketmq-shaded-slf4j-api-bridge.version} - org.apache.logging.log4j - log4j-slf4j-impl - 2.17.1 + io.github.aliyunmq + rocketmq-slf4j-api + ${rocketmq-logging.version} + + + io.github.aliyunmq + rocketmq-logback-classic + ${rocketmq-logging.version} commons-validator commons-validator - 1.7 + ${commons-validator.version} + + + com.github.luben + zstd-jni + ${zstd-jni.version} + + + org.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.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.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} + + + 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.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 + + + + + + + junit + junit + ${junit.version} + test + + + org.assertj + assertj-core + ${assertj-core.version} + test + + + org.mockito + mockito-core + ${mockito-core.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 + + diff --git a/proxy/BUILD.bazel b/proxy/BUILD.bazel new file mode 100644 index 00000000000..fcb85e46fb6 --- /dev/null +++ b/proxy/BUILD.bazel @@ -0,0 +1,119 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 = [ + "//acl", + "//broker", + "//client", + "//common", + "//remoting", + "//srvutil", + "@maven//:ch_qos_logback_logback_classic", + "@maven//:ch_qos_logback_logback_core", + "@maven//:com_alibaba_fastjson", + "@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_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_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", + ], + visibility = ["//visibility:public"], + deps = [ + "//acl", + ":proxy", + "//:test_deps", + "//broker", + "//client", + "//common", + "//remoting", + "@maven//:ch_qos_logback_logback_core", + "@maven//:com_alibaba_fastjson", + "@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//: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", + ], +) + +GenTestRules( + name = "GeneratedTestRules", + exclude_tests = [ + "src/test/java/org/apache/rocketmq/proxy/config/InitConfigTest", + ], + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], +) diff --git a/proxy/README.md b/proxy/README.md new file mode 100644 index 00000000000..936bd024b0b --- /dev/null +++ b/proxy/README.md @@ -0,0 +1,60 @@ +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. \ No newline at end of file diff --git a/proxy/pom.xml b/proxy/pom.xml new file mode 100644 index 00000000000..ff247f6e0b8 --- /dev/null +++ b/proxy/pom.xml @@ -0,0 +1,114 @@ + + + + + + rocketmq-all + org.apache.rocketmq + 5.1.3 + + + 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-acl + + + io.grpc + grpc-netty-shaded + + + io.grpc + grpc-protobuf + + + io.grpc + grpc-stub + + + io.grpc + grpc-services + + + com.google.protobuf + protobuf-java-util + + + 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 + + + \ No newline at end of file diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/CommandLineArgument.java b/proxy/src/main/java/org/apache/rocketmq/proxy/CommandLineArgument.java new file mode 100644 index 00000000000..0499f2659bb --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/CommandLineArgument.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyMode.java b/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyMode.java new file mode 100644 index 00000000000..3cc36425b06 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyMode.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java b/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java new file mode 100644 index 00000000000..ea13bb8082d --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.Date; +import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +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.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; +import org.apache.rocketmq.common.utils.StartAndShutdown; +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.remoting.protocol.RemotingCommand; +import org.apache.rocketmq.srvutil.ServerUtil; + +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(); + + // create grpcServer + GrpcServer grpcServer = GrpcServerBuilder.newBuilder(executor, ConfigurationManager.getProxyConfig().getGrpcServerPort()) + .addService(createServiceProcessor(messagingProcessor)) + .addService(ChannelzService.newInstance(100)) + .addService(ProtoReflectionService.newInstance()) + .configInterceptor() + .build(); + PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(grpcServer); + + RemotingProtocolServer remotingServer = new RemotingProtocolServer(messagingProcessor); + 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.intConfig(); + 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/AbstractCacheLoader.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/AbstractCacheLoader.java new file mode 100644 index 00000000000..581caffdbdd --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/AbstractCacheLoader.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} \ No newline at end of file diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/Address.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/Address.java new file mode 100644 index 00000000000..2fc1dab40ed --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/Address.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +public class Address { + + public enum AddressScheme { + IPv4, + IPv6, + DOMAIN_NAME, + UNRECOGNIZED + } + + private AddressScheme addressScheme; + private 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; + } + + @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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/ContextVariable.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ContextVariable.java new file mode 100644 index 00000000000..0760826de75 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ContextVariable.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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"; + +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/MessageReceiptHandle.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/MessageReceiptHandle.java new file mode 100644 index 00000000000..e885cf4c28b --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/MessageReceiptHandle.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 int reconsumeTimes; + + private final AtomicInteger renewRetryTimes = new AtomicInteger(0); + private final AtomicInteger renewTimes = new AtomicInteger(0); + private final long consumeTimestamp; + private volatile String receiptHandleStr; + + public MessageReceiptHandle(String group, String topic, int queueId, String receiptHandleStr, String messageId, + long queueOffset, int reconsumeTimes) { + ReceiptHandle receiptHandle = 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 = receiptHandle.getRetrieveTime(); + } + + @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) + .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(); + } + +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyContext.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyContext.java new file mode 100644 index 00000000000..77a6791f047 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyContext.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyException.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyException.java new file mode 100644 index 00000000000..af528329fd5 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyException.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyExceptionCode.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyExceptionCode.java new file mode 100644 index 00000000000..4f91388215c --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyExceptionCode.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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, +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/ReceiptHandleGroup.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ReceiptHandleGroup.java new file mode 100644 index 00000000000..05867c3348a --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ReceiptHandleGroup.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; +import org.apache.rocketmq.proxy.config.ConfigurationManager; + +public class ReceiptHandleGroup { + protected final Map> receiptHandleMap = new ConcurrentHashMap<>(); + + public static class HandleData { + private final Semaphore semaphore = new Semaphore(1); + private volatile boolean needRemove = false; + private volatile MessageReceiptHandle messageReceiptHandle; + + public HandleData(MessageReceiptHandle messageReceiptHandle) { + this.messageReceiptHandle = messageReceiptHandle; + } + + public boolean lock(long timeoutMs) { + try { + return this.semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + return false; + } + } + + public void unlock() { + this.semaphore.release(); + } + + @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, String handle, MessageReceiptHandle value) { + long timeout = ConfigurationManager.getProxyConfig().getLockTimeoutMsInHandleGroup(); + Map handleMap = ConcurrentHashMapUtils.computeIfAbsent((ConcurrentHashMap>) this.receiptHandleMap, + msgID, msgIDKey -> new ConcurrentHashMap<>()); + handleMap.compute(handle, (handleKey, handleData) -> { + if (handleData == null || handleData.needRemove) { + return new HandleData(value); + } + if (!handleData.lock(timeout)) { + 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(); + } + return handleData; + }); + } + + public boolean isEmpty() { + return this.receiptHandleMap.isEmpty(); + } + + 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(handle, (handleKey, handleData) -> { + if (!handleData.lock(timeout)) { + 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(); + } + 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(handle, (handleKey, handleData) -> { + if (!handleData.lock(timeout)) { + 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(); + } + }); + removeHandleMapKeyIfNeed(msgID); + return res.get(); + } + + public void computeIfPresent(String msgID, String handle, + Function> function) { + Map handleMap = this.receiptHandleMap.get(msgID); + if (handleMap == null) { + return; + } + long timeout = ConfigurationManager.getProxyConfig().getLockTimeoutMsInHandleGroup(); + handleMap.computeIfPresent(handle, (handleKey, handleData) -> { + if (!handleData.lock(timeout)) { + 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(); + } + 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((handleStr, v) -> { + scanner.onData(msgID, handleStr, v.messageReceiptHandle); + }); + }); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("receiptHandleMap", receiptHandleMap) + .toString(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/RenewStrategyPolicy.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/RenewStrategyPolicy.java new file mode 100644 index 00000000000..ce33619b4d2 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/RenewStrategyPolicy.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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]; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/channel/ChannelHelper.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/channel/ChannelHelper.java new file mode 100644 index 00000000000..dd15c85fb2b --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/channel/ChannelHelper.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ExceptionUtils.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ExceptionUtils.java new file mode 100644 index 00000000000..e85360a5daf --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ExceptionUtils.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FilterUtils.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FilterUtils.java new file mode 100644 index 00000000000..9e44aceaec0 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FilterUtils.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FutureUtils.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FutureUtils.java new file mode 100644 index 00000000000..ea50e64eeaa --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FutureUtils.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ProxyUtils.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ProxyUtils.java new file mode 100644 index 00000000000..7e82a49613b --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ProxyUtils.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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"; +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigFile.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigFile.java new file mode 100644 index 00000000000..37757f8d636 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigFile.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/Configuration.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/Configuration.java new file mode 100644 index 00000000000..2561d44190e --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/Configuration.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.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.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<>(); + 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); + } + + public static 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()) { + log.warn("the config file {} not exist", filePath); + throw new RuntimeException(String.format("the config file %s not exist", filePath)); + } + long fileLength = file.length(); + if (fileLength <= 0) { + log.warn("the config file {} length is zero", filePath); + throw new RuntimeException(String.format("the config file %s length is zero", filePath)); + } + + return new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); + } + + public ProxyConfig getProxyConfig() { + return proxyConfigReference.get(); + } + + public void setProxyConfig(ProxyConfig proxyConfig) { + proxyConfigReference.set(proxyConfig); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigurationManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigurationManager.java new file mode 100644 index 00000000000..911e1f28f2d --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigurationManager.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import org.apache.commons.lang3.StringUtils; +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 = System.getenv(MixAll.ROCKETMQ_HOME_ENV); + 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 intConfig() throws Exception { + configuration = new Configuration(); + configuration.init(); + } + + public static String getProxyHome() { + return proxyHome; + } + + public static ProxyConfig getProxyConfig() { + return configuration.getProxyConfig(); + } + + public static String formatProxyConfig() { + return JSON.toJSONString(ConfigurationManager.getProxyConfig(), + SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat, SerializerFeature.WriteNullListAsEmpty); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/MetricCollectorMode.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/MetricCollectorMode.java new file mode 100644 index 00000000000..fb5329012db --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/MetricCollectorMode.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java new file mode 100644 index 00000000000..4f57a7052ac --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java @@ -0,0 +1,1382 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.ConcurrentHashMap; +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 tlsCertPath = ConfigurationManager.getProxyHome() + "/conf/tls/rocketmq.crt"; + /** + * gRPC + */ + private String proxyMode = ProxyMode.CLUSTER.name(); + private Integer grpcServerPort = 8081; + 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; + /** + * 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; + + /** + * 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 topicRouteServiceCacheExpiredInSeconds = 20; + private int topicRouteServiceCacheMaxNum = 20000; + private int topicRouteServiceThreadPoolNums = PROCESSOR_NUMBER; + private int topicRouteServiceThreadPoolQueueCapacity = 5000; + + private int topicConfigCacheExpiredInSeconds = 20; + private int topicConfigCacheMaxNum = 20000; + private int subscriptionGroupConfigCacheExpiredInSeconds = 20; + private int subscriptionGroupConfigCacheMaxNum = 20000; + private int metadataThreadPoolNums = 3; + private int metadataThreadPoolQueueCapacity = 1000; + + 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 boolean enableACL = false; + + 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 Map delayLevelTable = new ConcurrentHashMap<>(); + + 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; + + 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; + + @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 ConcurrentHashMap<>(); + 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 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 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 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 getTopicRouteServiceCacheExpiredInSeconds() { + return topicRouteServiceCacheExpiredInSeconds; + } + + public void setTopicRouteServiceCacheExpiredInSeconds(int topicRouteServiceCacheExpiredInSeconds) { + this.topicRouteServiceCacheExpiredInSeconds = topicRouteServiceCacheExpiredInSeconds; + } + + 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 getTopicConfigCacheExpiredInSeconds() { + return topicConfigCacheExpiredInSeconds; + } + + public void setTopicConfigCacheExpiredInSeconds(int topicConfigCacheExpiredInSeconds) { + this.topicConfigCacheExpiredInSeconds = topicConfigCacheExpiredInSeconds; + } + + public int getTopicConfigCacheMaxNum() { + return topicConfigCacheMaxNum; + } + + public void setTopicConfigCacheMaxNum(int topicConfigCacheMaxNum) { + this.topicConfigCacheMaxNum = topicConfigCacheMaxNum; + } + + public int getSubscriptionGroupConfigCacheExpiredInSeconds() { + return subscriptionGroupConfigCacheExpiredInSeconds; + } + + public void setSubscriptionGroupConfigCacheExpiredInSeconds(int subscriptionGroupConfigCacheExpiredInSeconds) { + this.subscriptionGroupConfigCacheExpiredInSeconds = subscriptionGroupConfigCacheExpiredInSeconds; + } + + public int getSubscriptionGroupConfigCacheMaxNum() { + return subscriptionGroupConfigCacheMaxNum; + } + + public void setSubscriptionGroupConfigCacheMaxNum(int subscriptionGroupConfigCacheMaxNum) { + this.subscriptionGroupConfigCacheMaxNum = subscriptionGroupConfigCacheMaxNum; + } + + 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 isEnableACL() { + return enableACL; + } + + public void setEnableACL(boolean enableACL) { + this.enableACL = enableACL; + } + + 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 Map 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(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 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServer.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServer.java new file mode 100644 index 00000000000..1bffa3c0be1 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServer.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.concurrent.TimeUnit; +import io.grpc.Server; +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; + +public class GrpcServer implements StartAndShutdown { + private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); + + private final Server server; + + protected GrpcServer(Server server) { + this.server = server; + } + + public void start() throws Exception { + this.server.start(); + log.info("grpc server start successfully."); + } + + public void shutdown() { + try { + this.server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + log.info("grpc server shutdown successfully."); + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServerBuilder.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServerBuilder.java new file mode 100644 index 00000000000..0ca6a1fcbd5 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServerBuilder.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.List; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import org.apache.rocketmq.acl.AccessValidator; +import org.apache.rocketmq.acl.plain.PlainAccessValidator; +import org.apache.rocketmq.common.constant.LoggerName; +import org.apache.rocketmq.common.utils.ServiceProvider; +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.AuthenticationInterceptor; +import org.apache.rocketmq.proxy.grpc.interceptor.ContextInterceptor; +import org.apache.rocketmq.proxy.grpc.interceptor.GlobalExceptionInterceptor; +import org.apache.rocketmq.proxy.grpc.interceptor.HeaderInterceptor; + +public class GrpcServerBuilder { + private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); + protected NettyServerBuilder serverBuilder; + + public static GrpcServerBuilder newBuilder(ThreadPoolExecutor executor, int port) { + return new GrpcServerBuilder(executor, port); + } + + protected GrpcServerBuilder(ThreadPoolExecutor executor, int port) { + serverBuilder = NettyServerBuilder.forPort(port); + + serverBuilder.protocolNegotiator(new OptionalSSLProtocolNegotiator()); + + // 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: {}, tlsKeyPath: {}, tlsCertPath: {}, threadPool: {}, queueCapacity: {}, " + + "boosLoop: {}, workerLoop: {}, maxInboundMessageSize: {}", + port, bossLoopNum, workerLoopNum, maxInboundMessageSize); + } + + 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() { + return new GrpcServer(this.serverBuilder.build()); + } + + public GrpcServerBuilder configInterceptor() { + // grpc interceptors, including acl, logging etc. + List accessValidators = ServiceProvider.load(AccessValidator.class); + if (accessValidators.isEmpty()) { + log.info("ServiceProvider loaded no AccessValidator, using default org.apache.rocketmq.acl.plain.PlainAccessValidator"); + accessValidators.add(new PlainAccessValidator()); + } + + this.serverBuilder.intercept(new AuthenticationInterceptor(accessValidators)); + + this.serverBuilder + .intercept(new GlobalExceptionInterceptor()) + .intercept(new ContextInterceptor()) + .intercept(new HeaderInterceptor()); + + return this; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/OptionalSSLProtocolNegotiator.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/OptionalSSLProtocolNegotiator.java new file mode 100644 index 00000000000..670e1c1a212 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/OptionalSSLProtocolNegotiator.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.netty.buffer.ByteBuf; +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.handler.codec.ByteToMessageDecoder; +import io.grpc.netty.shaded.io.netty.handler.ssl.ClientAuth; +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.util.InsecureTrustManagerFactory; +import io.grpc.netty.shaded.io.netty.handler.ssl.util.SelfSignedCertificate; +import io.grpc.netty.shaded.io.netty.util.AsciiString; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +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.proxy.config.ConfigurationManager; +import org.apache.rocketmq.proxy.config.ProxyConfig; +import org.apache.rocketmq.remoting.common.TlsMode; +import org.apache.rocketmq.remoting.netty.TlsSystemConfig; + +public class OptionalSSLProtocolNegotiator implements InternalProtocolNegotiator.ProtocolNegotiator { + protected static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); + + /** + * the length of the ssl record header (in bytes) + */ + private static final int SSL_RECORD_HEADER_LENGTH = 5; + + private static SslContext sslContext; + + public OptionalSSLProtocolNegotiator() { + sslContext = loadSslContext(); + } + + @Override + public AsciiString scheme() { + return AsciiString.of("https"); + } + + @Override + public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) { + return new PortUnificationServerHandler(grpcHandler); + } + + @Override + public void close() {} + + private static SslContext loadSslContext() { + try { + ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); + if (proxyConfig.isTlsTestModeEnable()) { + SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); + return GrpcSslContexts.forServer(selfSignedCertificate.certificate(), + selfSignedCertificate.privateKey()) + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .clientAuth(ClientAuth.NONE) + .build(); + } else { + String tlsKeyPath = ConfigurationManager.getProxyConfig().getTlsKeyPath(); + String tlsCertPath = ConfigurationManager.getProxyConfig().getTlsCertPath(); + try (InputStream serverKeyInputStream = Files.newInputStream( + Paths.get(tlsKeyPath)); + InputStream serverCertificateStream = Files.newInputStream( + Paths.get(tlsCertPath))) { + SslContext res = GrpcSslContexts.forServer(serverCertificateStream, + serverKeyInputStream) + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .clientAuth(ClientAuth.NONE) + .build(); + log.info("grpc load TLS configured OK"); + return res; + } + } + } catch (Exception e) { + log.error("grpc tls set failed. msg: {}, e:", e.getMessage(), e); + throw new RuntimeException("grpc tls set failed: " + e.getMessage()); + } + } + + public static class PortUnificationServerHandler extends ByteToMessageDecoder { + + private final ChannelHandler ssl; + private final ChannelHandler plaintext; + + public PortUnificationServerHandler(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) + throws Exception { + 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 need 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(InternalProtocolNegotiationEvent.getDefault()); + ctx.pipeline().remove(this); + } catch (Exception e) { + log.error("process ssl protocol negotiator failed.", e); + throw e; + } + } + } +} \ No newline at end of file diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/AuthenticationInterceptor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/AuthenticationInterceptor.java new file mode 100644 index 00000000000..951ebf006b5 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/AuthenticationInterceptor.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.protobuf.GeneratedMessageV3; +import io.grpc.Context; +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 java.util.List; +import org.apache.rocketmq.acl.AccessResource; +import org.apache.rocketmq.acl.AccessValidator; +import org.apache.rocketmq.acl.common.AclException; +import org.apache.rocketmq.acl.common.AuthenticationHeader; +import org.apache.rocketmq.acl.plain.PlainAccessResource; +import org.apache.rocketmq.proxy.config.ConfigurationManager; + +public class AuthenticationInterceptor implements ServerInterceptor { + protected final List accessValidatorList; + + public AuthenticationInterceptor(List accessValidatorList) { + this.accessValidatorList = accessValidatorList; + } + + @Override + public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, + ServerCallHandler next) { + return new ForwardingServerCallListener.SimpleForwardingServerCallListener(next.startCall(call, headers)) { + @Override + public void onMessage(R message) { + GeneratedMessageV3 messageV3 = (GeneratedMessageV3) message; + headers.put(InterceptorConstants.RPC_NAME, messageV3.getDescriptorForType().getFullName()); + headers.put(InterceptorConstants.SIMPLE_RPC_NAME, messageV3.getDescriptorForType().getName()); + if (ConfigurationManager.getProxyConfig().isEnableACL()) { + try { + AuthenticationHeader authenticationHeader = AuthenticationHeader.builder() + .remoteAddress(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.REMOTE_ADDRESS)) + .namespace(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.NAMESPACE_ID)) + .authorization(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.AUTHORIZATION)) + .datetime(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.DATE_TIME)) + .sessionToken(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.SESSION_TOKEN)) + .requestId(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.REQUEST_ID)) + .language(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.LANGUAGE)) + .clientVersion(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.CLIENT_VERSION)) + .protocol(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.PROTOCOL_VERSION)) + .requestCode(RequestMapping.map(messageV3.getDescriptorForType().getFullName())) + .build(); + + validate(authenticationHeader, headers, messageV3); + super.onMessage(message); + } catch (AclException aclException) { + throw new StatusRuntimeException(Status.PERMISSION_DENIED, headers); + } + } else { + super.onMessage(message); + } + } + }; + } + + protected void validate(AuthenticationHeader authenticationHeader, Metadata headers, GeneratedMessageV3 messageV3) { + for (AccessValidator accessValidator : accessValidatorList) { + AccessResource accessResource = accessValidator.parse(messageV3, authenticationHeader); + accessValidator.validate(accessResource); + + if (accessResource instanceof PlainAccessResource) { + PlainAccessResource plainAccessResource = (PlainAccessResource) accessResource; + headers.put(InterceptorConstants.AUTHORIZATION_AK, plainAccessResource.getAccessKey()); + } + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/ContextInterceptor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/ContextInterceptor.java new file mode 100644 index 00000000000..07d7ab9bf38 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/ContextInterceptor.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +public class ContextInterceptor implements ServerInterceptor { + + @Override + public ServerCall.Listener interceptCall( + ServerCall call, + Metadata headers, + ServerCallHandler next + ) { + Context context = Context.current().withValue(InterceptorConstants.METADATA, headers); + return Contexts.interceptCall(context, call, headers, next); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/GlobalExceptionInterceptor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/GlobalExceptionInterceptor.java new file mode 100644 index 00000000000..3ce00b4ce87 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/GlobalExceptionInterceptor.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptor.java new file mode 100644 index 00000000000..1cbb0036103 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptor.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Grpc; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +public class HeaderInterceptor implements ServerInterceptor { + @Override + public ServerCall.Listener interceptCall( + ServerCall call, + Metadata headers, + ServerCallHandler next + ) { + SocketAddress remoteSocketAddress = call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); + String remoteAddress = parseSocketAddress(remoteSocketAddress); + headers.put(InterceptorConstants.REMOTE_ADDRESS, remoteAddress); + + SocketAddress localSocketAddress = call.getAttributes().get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR); + String localAddress = parseSocketAddress(localSocketAddress); + headers.put(InterceptorConstants.LOCAL_ADDRESS, localAddress); + 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 ""; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/InterceptorConstants.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/InterceptorConstants.java new file mode 100644 index 00000000000..768f3d96abc --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/InterceptorConstants.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Metadata; + +public class InterceptorConstants { + 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); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/RequestMapping.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/RequestMapping.java new file mode 100644 index 00000000000..866124d747c --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/RequestMapping.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.ReceiveMessageRequest; +import apache.rocketmq.v2.SendMessageRequest; +import java.util.HashMap; +import java.util.Map; +import org.apache.rocketmq.remoting.protocol.RequestCode; + +public class RequestMapping { + 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(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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/AbstractMessingActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/AbstractMessingActivity.java new file mode 100644 index 00000000000..6598b9e7e65 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/AbstractMessingActivity.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 AbstractMessingActivity { + protected static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); + protected final MessagingProcessor messagingProcessor; + protected final GrpcClientSettingsManager grpcClientSettingsManager; + protected final GrpcChannelManager grpcChannelManager; + + public AbstractMessingActivity(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 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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/DefaultGrpcMessingActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/DefaultGrpcMessingActivity.java new file mode 100644 index 00000000000..9d49e0e2caa --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/DefaultGrpcMessingActivity.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.ReceiveMessageRequest; +import apache.rocketmq.v2.ReceiveMessageResponse; +import apache.rocketmq.v2.SendMessageRequest; +import apache.rocketmq.v2.SendMessageResponse; +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.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; +import org.apache.rocketmq.proxy.processor.ReceiptHandleProcessor; + +public class DefaultGrpcMessingActivity extends AbstractStartAndShutdown implements GrpcMessingActivity { + private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); + + protected GrpcClientSettingsManager grpcClientSettingsManager; + protected GrpcChannelManager grpcChannelManager; + protected ReceiptHandleProcessor receiptHandleProcessor; + protected ReceiveMessageActivity receiveMessageActivity; + protected AckMessageActivity ackMessageActivity; + protected ChangeInvisibleDurationActivity changeInvisibleDurationActivity; + protected SendMessageActivity sendMessageActivity; + protected ForwardMessageToDLQActivity forwardMessageToDLQActivity; + protected EndTransactionActivity endTransactionActivity; + protected RouteActivity routeActivity; + protected ClientActivity clientActivity; + + protected DefaultGrpcMessingActivity(MessagingProcessor messagingProcessor) { + this.init(messagingProcessor); + } + + protected void init(MessagingProcessor messagingProcessor) { + this.grpcClientSettingsManager = new GrpcClientSettingsManager(messagingProcessor); + this.grpcChannelManager = new GrpcChannelManager(messagingProcessor.getProxyRelayService(), this.grpcClientSettingsManager); + this.receiptHandleProcessor = new ReceiptHandleProcessor(messagingProcessor); + + this.receiveMessageActivity = new ReceiveMessageActivity(messagingProcessor, receiptHandleProcessor, grpcClientSettingsManager, grpcChannelManager); + this.ackMessageActivity = new AckMessageActivity(messagingProcessor, receiptHandleProcessor, grpcClientSettingsManager, grpcChannelManager); + this.changeInvisibleDurationActivity = new ChangeInvisibleDurationActivity(messagingProcessor, receiptHandleProcessor, grpcClientSettingsManager, grpcChannelManager); + this.sendMessageActivity = new SendMessageActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); + this.forwardMessageToDLQActivity = new ForwardMessageToDLQActivity(messagingProcessor, receiptHandleProcessor, 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.receiptHandleProcessor); + 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 StreamObserver telemetry(ProxyContext ctx, + StreamObserver responseObserver) { + return this.clientActivity.telemetry(ctx, responseObserver); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplication.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplication.java new file mode 100644 index 00000000000..32395322a39 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplication.java @@ -0,0 +1,469 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.TelemetryCommand; +import io.grpc.Context; +import io.grpc.Metadata; +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.common.constant.LoggerName; +import org.apache.rocketmq.common.thread.ThreadPoolMonitor; +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.proxy.config.ProxyConfig; +import org.apache.rocketmq.proxy.grpc.interceptor.InterceptorConstants; +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; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +import org.apache.rocketmq.proxy.processor.channel.ChannelProtocolType; + +public class GrpcMessagingApplication extends MessagingServiceGrpc.MessagingServiceImplBase implements StartAndShutdown { + private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); + + private final GrpcMessingActivity grpcMessingActivity; + + protected ThreadPoolExecutor routeThreadPoolExecutor; + protected ThreadPoolExecutor producerThreadPoolExecutor; + protected ThreadPoolExecutor consumerThreadPoolExecutor; + protected ThreadPoolExecutor clientManagerThreadPoolExecutor; + protected ThreadPoolExecutor transactionThreadPoolExecutor; + + protected GrpcMessagingApplication(GrpcMessingActivity grpcMessingActivity) { + this.grpcMessingActivity = grpcMessingActivity; + + 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.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) { + return new GrpcMessagingApplication(new DefaultGrpcMessingActivity( + messagingProcessor + )); + } + + 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) { + 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() { + Context ctx = Context.current(); + Metadata headers = InterceptorConstants.METADATA.get(ctx); + ProxyContext context = ProxyContext.create() + .setLocalAddress(getDefaultStringMetadataInfo(headers, InterceptorConstants.LOCAL_ADDRESS)) + .setRemoteAddress(getDefaultStringMetadataInfo(headers, InterceptorConstants.REMOTE_ADDRESS)) + .setClientID(getDefaultStringMetadataInfo(headers, InterceptorConstants.CLIENT_ID)) + .setProtocolType(ChannelProtocolType.GRPC_V2.getName()) + .setLanguage(getDefaultStringMetadataInfo(headers, InterceptorConstants.LANGUAGE)) + .setClientVersion(getDefaultStringMetadataInfo(headers, InterceptorConstants.CLIENT_VERSION)) + .setAction(getDefaultStringMetadataInfo(headers, InterceptorConstants.SIMPLE_RPC_NAME)); + if (ctx.getDeadline() != null) { + context.setRemainingMs(ctx.getDeadline().timeRemaining(TimeUnit.MILLISECONDS)); + } + return context; + } + + protected void validateContext(ProxyContext context) { + if (StringUtils.isBlank(context.getClientID())) { + throw new GrpcProxyException(Code.CLIENT_ID_REQUIRED, "client id cannot be empty"); + } + } + + protected String getDefaultStringMetadataInfo(Metadata headers, Metadata.Key key) { + return StringUtils.defaultString(headers.get(key)); + } + + @Override + public void queryRoute(QueryRouteRequest request, StreamObserver responseObserver) { + Function statusResponseCreator = status -> QueryRouteResponse.newBuilder().setStatus(status).build(); + ProxyContext context = createContext(); + try { + validateContext(context); + this.addExecutor(this.routeThreadPoolExecutor, + context, + request, + () -> grpcMessingActivity.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 { + validateContext(context); + this.addExecutor(this.clientManagerThreadPoolExecutor, + context, + request, + () -> grpcMessingActivity.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 { + validateContext(context); + this.addExecutor(this.producerThreadPoolExecutor, + context, + request, + () -> grpcMessingActivity.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 { + validateContext(context); + this.addExecutor(this.routeThreadPoolExecutor, + context, + request, + () -> grpcMessingActivity.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 { + validateContext(context); + this.addExecutor(this.consumerThreadPoolExecutor, + context, + request, + () -> grpcMessingActivity.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 { + validateContext(context); + this.addExecutor(this.consumerThreadPoolExecutor, + context, + request, + () -> grpcMessingActivity.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 { + validateContext(context); + this.addExecutor(this.producerThreadPoolExecutor, + context, + request, + () -> grpcMessingActivity.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 { + validateContext(context); + this.addExecutor(this.transactionThreadPoolExecutor, + context, + request, + () -> grpcMessingActivity.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 { + validateContext(context); + this.addExecutor(this.clientManagerThreadPoolExecutor, + context, + request, + () -> grpcMessingActivity.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.newBuilder().setStatus(status).build(); + ProxyContext context = createContext(); + try { + validateContext(context); + this.addExecutor(this.consumerThreadPoolExecutor, + context, + request, + () -> grpcMessingActivity.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 StreamObserver telemetry(StreamObserver responseObserver) { + Function statusResponseCreator = status -> TelemetryCommand.newBuilder().setStatus(status).build(); + ProxyContext context = createContext(); + StreamObserver responseTelemetryCommand = grpcMessingActivity.telemetry(context, responseObserver); + return new StreamObserver() { + @Override + public void onNext(TelemetryCommand value) { + try { + validateContext(context); + addExecutor(clientManagerThreadPoolExecutor, + context, + value, + () -> responseTelemetryCommand.onNext(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.grpcMessingActivity.shutdown(); + + this.routeThreadPoolExecutor.shutdown(); + this.routeThreadPoolExecutor.shutdown(); + this.producerThreadPoolExecutor.shutdown(); + this.consumerThreadPoolExecutor.shutdown(); + this.clientManagerThreadPoolExecutor.shutdown(); + this.transactionThreadPoolExecutor.shutdown(); + } + + @Override + public void start() throws Exception { + this.grpcMessingActivity.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); + } + } + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessingActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessingActivity.java new file mode 100644 index 00000000000..8f1db82307a --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessingActivity.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.ReceiveMessageRequest; +import apache.rocketmq.v2.ReceiveMessageResponse; +import apache.rocketmq.v2.SendMessageRequest; +import apache.rocketmq.v2.SendMessageResponse; +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 GrpcMessingActivity 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); + + StreamObserver telemetry(ProxyContext ctx, StreamObserver responseObserver); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcChannelManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcChannelManager.java new file mode 100644 index 00000000000..14330dd8d48 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcChannelManager.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Executors; +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.proxy.common.ProxyContext; +import org.apache.rocketmq.common.utils.StartAndShutdown; +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 = Executors.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; + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcClientChannel.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcClientChannel.java new file mode 100644 index 00000000000..714d0bf019e --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcClientChannel.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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; + +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 processGetConsumerRunningInfo(RemotingCommand command, + GetConsumerRunningInfoRequestHeader header, + CompletableFuture> responseFuture) { + if (!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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/client/ClientActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/client/ClientActivity.java new file mode 100644 index 00000000000..a60228eb9f8 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/client/ClientActivity.java @@ -0,0 +1,482 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.TelemetryCommand; +import apache.rocketmq.v2.ThreadStackTrace; +import apache.rocketmq.v2.VerifyMessageResult; +import io.grpc.StatusRuntimeException; +import io.grpc.stub.StreamObserver; +import io.netty.channel.Channel; +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.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.AbstractMessingActivity; +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 AbstractMessingActivity { + + 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 = GrpcConverter.getInstance().wrapResourceWithNamespace(topic); + this.registerProducer(ctx, topicName); + } + break; + } + case PUSH_CONSUMER: + case SIMPLE_CONSUMER: { + validateConsumerGroup(request.getGroup()); + String consumerGroup = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup()); + 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); + + switch (clientSettings.getClientType()) { + case PRODUCER: + for (Resource topic : clientSettings.getPublishing().getTopicsList()) { + String topicName = GrpcConverter.getInstance().wrapResourceWithNamespace(topic); + 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 SIMPLE_CONSUMER: + validateConsumerGroup(request.getGroup()); + String consumerGroup = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup()); + 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); + } + 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 StreamObserver telemetry(ProxyContext ctx, + StreamObserver responseObserver) { + return new StreamObserver() { + @Override + public void onNext(TelemetryCommand request) { + 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); + } + + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + }; + } + + 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 = GrpcConverter.getInstance().wrapResourceWithNamespace(topic); + grpcClientChannel = registerProducer(ctx, topicName); + grpcClientChannel.setClientObserver(responseObserver); + } + break; + case SUBSCRIPTION: + validateConsumerGroup(settings.getSubscription().getGroup()); + String groupName = GrpcConverter.getInstance().wrapResourceWithNamespace(settings.getSubscription().getGroup()); + 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(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), + MessageModel.CLUSTERING, + 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: + return ConsumeType.CONSUME_PASSIVELY; + default: + throw new IllegalArgumentException("Client type is not consumer, type: " + clientType); + } + } + + protected Set buildSubscriptionDataSet(List subscriptionEntryList) { + Set subscriptionDataSet = new HashSet<>(); + for (SubscriptionEntry sub : subscriptionEntryList) { + String topicName = GrpcConverter.getInstance().wrapResourceWithNamespace(sub.getTopic()); + 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(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.removeClientSettings(clientChannelInfo.getClientId()); + } + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManager.java new file mode 100644 index 00000000000..af8b4546e1e --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManager.java @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.function.Function; +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.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +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.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 = CLIENT_SETTINGS_MAP.get(clientId); + if (settings == null) { + return null; + } + if (settings.hasPublishing()) { + settings = mergeProducerData(settings); + } else if (settings.hasSubscription()) { + settings = mergeSubscriptionData(ctx, settings, + GrpcConverter.getInstance().wrapResourceWithNamespace(settings.getSubscription().getGroup())); + } + 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 config = ConfigurationManager.getProxyConfig(); + + resultSettingsBuilder.getSubscriptionBuilder() + .setReceiveBatchSize(config.getGrpcClientConsumerLongPollingBatchSize()) + .setLongPollingTimeout(Durations.fromMillis(config.getGrpcClientConsumerMaxLongPollingTimeoutMillis())) + .setFifo(groupConfig.isConsumeMessageOrderly()); + + 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(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 void removeClientSettings(String clientId) { + CLIENT_SETTINGS_MAP.remove(clientId); + } + + public void computeIfPresent(String clientId, Function function) { + CLIENT_SETTINGS_MAP.computeIfPresent(clientId, (clientIdKey, value) -> function.apply(value)); + } + + public Settings removeAndGetClientSettings(ProxyContext ctx) { + String clientId = ctx.getClientID(); + Settings settings = CLIENT_SETTINGS_MAP.remove(clientId); + if (settings == null) { + return null; + } + if (settings.hasSubscription()) { + settings = mergeSubscriptionData(ctx, settings, + GrpcConverter.getInstance().wrapResourceWithNamespace(settings.getSubscription().getGroup())); + } + return mergeMetric(settings); + } + + @Override + public String getServiceName() { + return "GrpcClientSettingsManagerCleaner"; + } + + @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)) { + return settings; + } + String consumerGroup = GrpcConverter.getInstance().wrapResourceWithNamespace(settings.getSubscription().getGroup()); + ConsumerGroupInfo consumerGroupInfo = this.messagingProcessor.getConsumerGroupInfo(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); + } + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcConverter.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcConverter.java new file mode 100644 index 00000000000..4daf8351196 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcConverter.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 String wrapResourceWithNamespace(Resource resource) { + return NamespaceUtil.wrapNamespace(resource.getResourceNamespace(), resource.getName()); + } + + 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 + String isTrans = messageExt.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED); + String isTransValue = "true"; + if (isTransValue.equals(isTrans)) { + systemPropertiesBuilder.setMessageType(MessageType.TRANSACTION); + } else if (messageExt.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null + || messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS) != null + || messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null) { + systemPropertiesBuilder.setMessageType(MessageType.DELAY); + } else if (messageExt.getProperty(MessageConst.PROPERTY_SHARDING_KEY) != null) { + systemPropertiesBuilder.setMessageType(MessageType.FIFO); + } else { + systemPropertiesBuilder.setMessageType(MessageType.NORMAL); + } + + // 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)); + } + } + + // sharding key + String shardingKey = messageExt.getProperty(MessageConst.PROPERTY_SHARDING_KEY); + if (shardingKey != null) { + systemPropertiesBuilder.setMessageGroup(shardingKey); + } + + // 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcProxyException.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcProxyException.java new file mode 100644 index 00000000000..74e499b4d72 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcProxyException.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcValidator.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcValidator.java new file mode 100644 index 00000000000..cfcd2a26938 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcValidator.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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(GrpcConverter.getInstance().wrapResourceWithNamespace(topic)); + } + + 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(GrpcConverter.getInstance().wrapResourceWithNamespace(consumerGroup)); + } + + 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseBuilder.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseBuilder.java new file mode 100644 index 00000000000..0b3c85ea674 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseBuilder.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.proxy.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(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()); + } + + 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) + .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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseWriter.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseWriter.java new file mode 100644 index 00000000000..3ac3d48410e --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseWriter.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} + diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivity.java new file mode 100644 index 00000000000..993f069b947 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivity.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.grpc.v2.AbstractMessingActivity; +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.GrpcConverter; +import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; +import org.apache.rocketmq.proxy.processor.MessagingProcessor; +import org.apache.rocketmq.proxy.processor.ReceiptHandleProcessor; + +public class AckMessageActivity extends AbstractMessingActivity { + protected ReceiptHandleProcessor receiptHandleProcessor; + + public AckMessageActivity(MessagingProcessor messagingProcessor, ReceiptHandleProcessor receiptHandleProcessor, + GrpcClientSettingsManager grpcClientSettingsManager, + GrpcChannelManager grpcChannelManager) { + super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); + this.receiptHandleProcessor = receiptHandleProcessor; + } + + public CompletableFuture ackMessage(ProxyContext ctx, AckMessageRequest request) { + CompletableFuture future = new CompletableFuture<>(); + + try { + validateTopicAndConsumerGroup(request.getTopic(), request.getGroup()); + + CompletableFuture[] futures = new CompletableFuture[request.getEntriesCount()]; + for (int i = 0; i < request.getEntriesCount(); i++) { + futures[i] = processAckMessage(ctx, request, request.getEntries(i)); + } + CompletableFuture.allOf(futures).whenComplete((val, throwable) -> { + if (throwable != null) { + future.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); + 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")); + } + future.complete(responseBuilder.build()); + }); + } catch (Throwable t) { + future.completeExceptionally(t); + } + return future; + } + + protected CompletableFuture processAckMessage(ProxyContext ctx, AckMessageRequest request, + AckMessageEntry ackMessageEntry) { + CompletableFuture future = new CompletableFuture<>(); + + try { + String handleString = ackMessageEntry.getReceiptHandle(); + + String group = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup()); + MessageReceiptHandle messageReceiptHandle = receiptHandleProcessor.removeReceiptHandle(ctx, grpcChannelManager.getChannel(ctx.getClientID()), group, ackMessageEntry.getMessageId(), ackMessageEntry.getReceiptHandle()); + if (messageReceiptHandle != null) { + handleString = messageReceiptHandle.getReceiptHandleStr(); + } + CompletableFuture ackResultFuture = this.messagingProcessor.ackMessage( + ctx, + ReceiptHandle.decode(handleString), + ackMessageEntry.getMessageId(), + group, + GrpcConverter.getInstance().wrapResourceWithNamespace(request.getTopic())); + 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ChangeInvisibleDurationActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ChangeInvisibleDurationActivity.java new file mode 100644 index 00000000000..9b7e947e0ba --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ChangeInvisibleDurationActivity.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.AbstractMessingActivity; +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.GrpcConverter; +import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; +import org.apache.rocketmq.proxy.processor.MessagingProcessor; +import org.apache.rocketmq.proxy.processor.ReceiptHandleProcessor; + +public class ChangeInvisibleDurationActivity extends AbstractMessingActivity { + protected ReceiptHandleProcessor receiptHandleProcessor; + + public ChangeInvisibleDurationActivity(MessagingProcessor messagingProcessor, + ReceiptHandleProcessor receiptHandleProcessor, + GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { + super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); + this.receiptHandleProcessor = receiptHandleProcessor; + } + + 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 = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup()); + + MessageReceiptHandle messageReceiptHandle = receiptHandleProcessor.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, + GrpcConverter.getInstance().wrapResourceWithNamespace(request.getTopic()), + 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/PopMessageResultFilterImpl.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/PopMessageResultFilterImpl.java new file mode 100644 index 00000000000..b31106164e8 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/PopMessageResultFilterImpl.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivity.java new file mode 100644 index 00000000000..22a149004ce --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivity.java @@ -0,0 +1,198 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 org.apache.commons.lang3.StringUtils; +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.AbstractMessingActivity; +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.GrpcConverter; +import org.apache.rocketmq.proxy.processor.MessagingProcessor; +import org.apache.rocketmq.proxy.processor.QueueSelector; +import org.apache.rocketmq.proxy.processor.ReceiptHandleProcessor; +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 AbstractMessingActivity { + protected ReceiptHandleProcessor receiptHandleProcessor; + private static final String ILLEGAL_POLLING_TIME_INTRODUCED_CLIENT_VERSION = "5.0.3"; + + public ReceiveMessageActivity(MessagingProcessor messagingProcessor, ReceiptHandleProcessor receiptHandleProcessor, + GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { + super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); + this.receiptHandleProcessor = receiptHandleProcessor; + } + + public void receiveMessage(ProxyContext ctx, ReceiveMessageRequest request, + StreamObserver responseObserver) { + ReceiveMessageResponseStreamWriter writer = createWriter(ctx, responseObserver); + + try { + Settings settings = this.grpcClientSettingsManager.getClientSettings(ctx); + 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 = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getMessageQueue().getTopic()); + String group = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup()); + + 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; + } + + this.messagingProcessor.popMessage( + ctx, + new ReceiveMessageQueueSelector( + request.getMessageQueue().getBroker().getName() + ), + group, + topic, + request.getBatchSize(), + actualInvisibleTime, + pollingTime, + ConsumeInitMode.MAX, + subscriptionData, + fifo, + new PopMessageResultFilterImpl(maxAttempts), + timeRemaining + ).thenAccept(popResult -> { + if (proxyConfig.isEnableProxyAutoRenew() && request.getAutoRenew()) { + if (PopStatus.FOUND.equals(popResult.getPopStatus())) { + 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()); + receiptHandleProcessor.addReceiptHandle(ctx, grpcChannelManager.getChannel(ctx.getClientID()), group, messageExt.getMsgId(), receiptHandle, messageReceiptHandle); + } + } + } + } + writer.writeAndComplete(ctx, request, popResult); + }) + .exceptionally(t -> { + writer.writeAndComplete(ctx, request, t); + return null; + }); + } catch (Throwable t) { + writer.writeAndComplete(ctx, request, t); + } + } + + 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; + } + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageResponseStreamWriter.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageResponseStreamWriter.java new file mode 100644 index 00000000000..7b8e70cf1dc --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageResponseStreamWriter.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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) { + 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 { + streamObserver.onNext(ReceiveMessageResponse.newBuilder() + .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name())) + .build()); + 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; + } + } 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(), + GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup()), + GrpcConverter.getInstance().wrapResourceWithNamespace(request.getMessageQueue().getTopic()), + NACK_INVISIBLE_TIME + ); + } + + 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); + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/ForwardMessageToDLQActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/ForwardMessageToDLQActivity.java new file mode 100644 index 00000000000..6b5c5c7e07b --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/ForwardMessageToDLQActivity.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.AbstractMessingActivity; +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.GrpcConverter; +import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; +import org.apache.rocketmq.proxy.processor.MessagingProcessor; +import org.apache.rocketmq.proxy.processor.ReceiptHandleProcessor; +import org.apache.rocketmq.remoting.protocol.RemotingCommand; + +public class ForwardMessageToDLQActivity extends AbstractMessingActivity { + protected ReceiptHandleProcessor receiptHandleProcessor; + + public ForwardMessageToDLQActivity(MessagingProcessor messagingProcessor, ReceiptHandleProcessor receiptHandleProcessor, + GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) { + super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); + this.receiptHandleProcessor = receiptHandleProcessor; + } + + public CompletableFuture forwardMessageToDeadLetterQueue(ProxyContext ctx, + ForwardMessageToDeadLetterQueueRequest request) { + CompletableFuture future = new CompletableFuture<>(); + try { + validateTopicAndConsumerGroup(request.getTopic(), request.getGroup()); + + String group = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup()); + String handleString = request.getReceiptHandle(); + MessageReceiptHandle messageReceiptHandle = receiptHandleProcessor.removeReceiptHandle(ctx, grpcChannelManager.getChannel(ctx.getClientID()), group, request.getMessageId(), request.getReceiptHandle()); + if (messageReceiptHandle != null) { + handleString = messageReceiptHandle.getReceiptHandleStr(); + } + ReceiptHandle receiptHandle = ReceiptHandle.decode(handleString); + + return this.messagingProcessor.forwardMessageToDeadLetterQueue( + ctx, + receiptHandle, + request.getMessageId(), + GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup()), + GrpcConverter.getInstance().wrapResourceWithNamespace(request.getTopic()) + ).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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java new file mode 100644 index 00000000000..6146c80cd0f --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java @@ -0,0 +1,394 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.AbstractMessingActivity; +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.GrpcConverter; +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 AbstractMessingActivity { + + 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), + GrpcConverter.getInstance().wrapResourceWithNamespace(topic), + 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 = GrpcConverter.getInstance().wrapResourceWithNamespace(topic); + 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 = GrpcConverter.getInstance().wrapResourceWithNamespace(protoMessage.getTopic()); + + 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) { + 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 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 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())) + .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(); + } + 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().selectOne(false); + } + return targetMessageQueue; + } catch (Exception e) { + return null; + } + } + } + +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/route/RouteActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/route/RouteActivity.java new file mode 100644 index 00000000000..c5d485691b6 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/route/RouteActivity.java @@ -0,0 +1,302 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +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.AbstractMessingActivity; +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.GrpcConverter; +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 AbstractMessingActivity { + + 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(request.getEndpoints()); + + String topicName = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getTopic()); + 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(request.getEndpoints()); + + ProxyTopicRouteData proxyTopicRouteData = this.messagingProcessor.getTopicRouteDataForProxy( + ctx, + addressList, + GrpcConverter.getInstance().wrapResourceWithNamespace(request.getTopic())); + + boolean fifo = false; + SubscriptionGroupConfig config = this.messagingProcessor.getSubscriptionGroupConfig(ctx, + GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup())); + if (config != null && config.isConsumeMessageOrderly()) { + fifo = true; + } + + 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 (fifo) { + 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(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))); + } + + 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; + 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(); + } + + // 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) + .addAcceptMessageTypes(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) + .addAcceptMessageTypes(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) + .addAcceptMessageTypes(parseTopicMessageType(topicMessageType)) + .build(); + messageQueueList.add(messageQueue); + } + + return messageQueueList; + } + + private MessageType parseTopicMessageType(TopicMessageType topicMessageType) { + switch (topicMessageType) { + case NORMAL: + return MessageType.NORMAL; + case FIFO: + return MessageType.FIFO; + case TRANSACTION: + return MessageType.TRANSACTION; + case DELAY: + return MessageType.DELAY; + default: + return MessageType.MESSAGE_TYPE_UNSPECIFIED; + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/transaction/EndTransactionActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/transaction/EndTransactionActivity.java new file mode 100644 index 00000000000..e65cf2eb4f8 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/transaction/EndTransactionActivity.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.AbstractMessingActivity; +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.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.processor.TransactionStatus; + +public class EndTransactionActivity extends AbstractMessingActivity { + + 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.getTransactionId(), + request.getMessageId(), + GrpcConverter.getInstance().wrapResourceWithNamespace(request.getTopic()), + 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsConstant.java b/proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsConstant.java new file mode 100644 index 00000000000..a7682e6cf93 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsConstant.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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"; +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsManager.java new file mode 100644 index 00000000000..f5050858f61 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsManager.java @@ -0,0 +1,257 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; +import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; +import io.opentelemetry.exporter.logging.LoggingMetricExporter; +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.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.proxy.config.ProxyConfig; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +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 LoggingMetricExporter 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 = LoggingMetricExporter.create(proxyConfig.isMetricsInDelta() ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE); + java.util.logging.Logger.getLogger(LoggingMetricExporter.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(); + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/AbstractProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/AbstractProcessor.java new file mode 100644 index 00000000000..b61c3df9e52 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/AbstractProcessor.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + + public AbstractProcessor(MessagingProcessor messagingProcessor, + ServiceManager serviceManager) { + this.messagingProcessor = messagingProcessor; + this.serviceManager = serviceManager; + } + + protected void validateReceiptHandle(ReceiptHandle handle) { + if (handle.isExpired()) { + throw new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "receipt handle is expired"); + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ClientProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ClientProcessor.java new file mode 100644 index 00000000000..8fb6eaf7df6 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ClientProcessor.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.Set; +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.consumer.ConsumeFromWhere; +import org.apache.rocketmq.proxy.common.ProxyContext; +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; + +public class ClientProcessor extends AbstractProcessor { + + public ClientProcessor(MessagingProcessor messagingProcessor, + ServiceManager serviceManager) { + super(messagingProcessor, serviceManager); + } + + 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 + ) { + this.serviceManager.getConsumerManager().registerConsumer( + consumerGroup, + clientChannelInfo, + consumeType, + messageModel, + consumeFromWhere, + subList, + false, + updateSubscription); + } + + 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(String consumerGroup) { + return this.serviceManager.getConsumerManager().getConsumerGroupInfo(consumerGroup); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ConsumerProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ConsumerProcessor.java new file mode 100644 index 00000000000..c860ee8a1a9 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ConsumerProcessor.java @@ -0,0 +1,442 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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.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.FutureUtils; +import org.apache.rocketmq.proxy.common.utils.ProxyUtils; +import org.apache.rocketmq.proxy.service.ServiceManager; +import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue; +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.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, + 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, 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, + 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); + + future = this.serviceManager.getMessageService().popMessage( + ctx, + messageQueue, + requestHeader, + timeoutMillis) + .thenApplyAsync(popResult -> { + if (PopStatus.FOUND.equals(popResult.getPopStatus()) && + popResult.getMsgFoundList() != null && + !popResult.getMsgFoundList().isEmpty() && + 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); + + 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, + MessagingProcessor.DEFAULT_TIMEOUT_MILLS); + break; + case TO_DLQ: + this.messagingProcessor.forwardMessageToDeadLetterQueue( + ctx, + ReceiptHandle.decode(handleString), + messageExt.getMsgId(), + consumerGroup, + topic, + MessagingProcessor.DEFAULT_TIMEOUT_MILLS); + 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; + }, this.executor); + } catch (Throwable t) { + future.completeExceptionally(t); + } + return FutureUtils.addExecutor(future, this.executor); + } + + 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, + 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()); + + future = this.serviceManager.getMessageService().ackMessage( + ctx, + handle, + messageId, + ackMessageRequestHeader, + timeoutMillis); + } catch (Throwable t) { + future.completeExceptionally(t); + } + return FutureUtils.addExecutor(future, this.executor); + } + + public CompletableFuture changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, + String messageId, String groupName, String topicName, long invisibleTime, long timeoutMillis) { + 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); + 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); + } + return FutureUtils.addExecutor(future, this.executor); + } + + 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 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<>(); + 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", t); + } + future.complete(successSet); + }); + return FutureUtils.addExecutor(future, this.executor); + } + + public CompletableFuture unlockBatchMQ(ProxyContext ctx, Set mqSet, + String consumerGroup, String clientId, long timeoutMillis) { + CompletableFuture future = new CompletableFuture<>(); + 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", t); + } + future.complete(null); + }); + 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) { + return mqSet.stream().map(mq -> { + try { + return serviceManager.getTopicRouteService().buildAddressableMessageQueue(ctx, mq); + } catch (Exception e) { + return null; + } + }).collect(Collectors.toSet()); + } + + protected HashMap> buildAddressableMapByBrokerName( + final Set mqSet) { + HashMap> result = new HashMap<>(); + for (AddressableMessageQueue mq : mqSet) { + List mqs = result.computeIfAbsent(mq.getBrokerName(), k -> new ArrayList<>()); + mqs.add(mq); + } + return result; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/DefaultMessagingProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/DefaultMessagingProcessor.java new file mode 100644 index 00000000000..81d2b9df359 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/DefaultMessagingProcessor.java @@ -0,0 +1,310 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.rocketmq.acl.common.AclUtils; +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.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.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.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 ThreadPoolExecutor producerProcessorExecutor; + protected ThreadPoolExecutor consumerProcessorExecutor; + protected static final String ROCKETMQ_HOME = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, + System.getenv(MixAll.ROCKETMQ_HOME_ENV)); + + 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.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()) { + 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.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, timeoutMillis); + } + + @Override + public CompletableFuture endTransaction(ProxyContext ctx, String transactionId, String messageId, String producerGroup, + TransactionStatus transactionStatus, boolean fromTransactionCheck, + long timeoutMillis) { + return this.transactionProcessor.endTransaction(ctx, 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, + long timeoutMillis + ) { + return this.consumerProcessor.popMessage(ctx, queueSelector, consumerGroup, topic, maxMsgNums, + invisibleTime, pollTime, initMode, subscriptionData, fifo, popMessageResultFilter, 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, timeoutMillis); + } + + @Override + public CompletableFuture changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, String messageId, + String groupName, String topicName, long invisibleTime, long timeoutMillis) { + return this.consumerProcessor.changeInvisibleTime(ctx, handle, messageId, groupName, topicName, invisibleTime, timeoutMillis); + } + + @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 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 request(ProxyContext ctx, String brokerName, RemotingCommand request, + long timeoutMillis) { + return this.requestBrokerProcessor.request(ctx, brokerName, request, timeoutMillis); + } + + @Override + public CompletableFuture requestOneway(ProxyContext ctx, String brokerName, RemotingCommand request, + long timeoutMillis) { + return this.requestBrokerProcessor.requestOneway(ctx, brokerName, request, timeoutMillis); + } + + @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(String consumerGroup) { + return this.clientProcessor.getConsumerGroupInfo(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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/MessagingProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/MessagingProcessor.java new file mode 100644 index 00000000000..98683a5154f --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/MessagingProcessor.java @@ -0,0 +1,301 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.message.Message; +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.common.utils.StartAndShutdown; +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(); + + 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 endTransaction( + ProxyContext ctx, + String transactionId, + String messageId, + String producerGroup, + TransactionStatus transactionStatus, + boolean fromTransactionCheck + ) { + return endTransaction(ctx, transactionId, messageId, producerGroup, transactionStatus, fromTransactionCheck, DEFAULT_TIMEOUT_MILLS); + } + + CompletableFuture endTransaction( + ProxyContext ctx, + 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, + 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 changeInvisibleTime( + ProxyContext ctx, + ReceiptHandle handle, + String messageId, + String groupName, + String topicName, + long invisibleTime + ) { + return changeInvisibleTime(ctx, handle, messageId, groupName, topicName, invisibleTime, DEFAULT_TIMEOUT_MILLS); + } + + CompletableFuture changeInvisibleTime( + ProxyContext ctx, + ReceiptHandle handle, + String messageId, + String groupName, + String topicName, + long invisibleTime, + long timeoutMillis + ); + + 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 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 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(String consumerGroup); + + void addTransactionSubscription( + ProxyContext ctx, + String producerGroup, + String topic + ); + + ProxyRelayService getProxyRelayService(); + + MetadataService getMetadataService(); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/PopMessageResultFilter.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/PopMessageResultFilter.java new file mode 100644 index 00000000000..09c1a0bf1a6 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/PopMessageResultFilter.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + } + + FilterResult filterMessage(ProxyContext ctx, String consumerGroup, SubscriptionData subscriptionData, + MessageExt messageExt); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java new file mode 100644 index 00000000000..0d0c6216862 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java @@ -0,0 +1,222 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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.proxy.common.ProxyContext; +import org.apache.rocketmq.proxy.common.ProxyException; +import org.apache.rocketmq.proxy.common.ProxyExceptionCode; +import org.apache.rocketmq.proxy.common.utils.FutureUtils; +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.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<>(); + try { + Message message = messageList.get(0); + String topic = message.getTopic(); + if (ConfigurationManager.getProxyConfig().isEnableTopicMessageTypeCheck()) { + 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); + } + } + } + AddressableMessageQueue 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()); + + future = this.serviceManager.getMessageService().sendMessage( + ctx, + messageQueue, + messageList, + requestHeader, + timeoutMillis) + .thenApplyAsync(sendResultList -> { + 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, messageQueue, sendResult, messageList); + } + } + return sendResultList; + }, this.executor); + } 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(), + 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, 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, timeoutMillis); + } + }, this.executor); + } catch (Throwable t) { + future.completeExceptionally(t); + } + return FutureUtils.addExecutor(future, this.executor); + } + +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/QueueSelector.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/QueueSelector.java new file mode 100644 index 00000000000..5fe0d1c38a2 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/QueueSelector.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ReceiptHandleProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ReceiptHandleProcessor.java new file mode 100644 index 00000000000..7fe97db7985 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ReceiptHandleProcessor.java @@ -0,0 +1,357 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.base.MoreObjects; +import com.google.common.base.Objects; +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.Executors; +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.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.thread.ThreadPoolMonitor; +import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils; +import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; +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.common.utils.StartAndShutdown; +import org.apache.rocketmq.proxy.common.channel.ChannelHelper; +import org.apache.rocketmq.proxy.common.utils.ExceptionUtils; +import org.apache.rocketmq.proxy.config.ConfigurationManager; +import org.apache.rocketmq.proxy.config.ProxyConfig; +import org.apache.rocketmq.remoting.protocol.subscription.RetryPolicy; +import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; + +public class ReceiptHandleProcessor extends AbstractStartAndShutdown { + protected final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); + protected final ConcurrentMap receiptHandleGroupMap; + protected final ScheduledExecutorService scheduledExecutorService = + Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("RenewalScheduledThread_")); + protected ThreadPoolExecutor renewalWorkerService; + protected final MessagingProcessor messagingProcessor; + protected final static RetryPolicy RENEW_POLICY = new RenewStrategyPolicy(); + + public ReceiptHandleProcessor(MessagingProcessor messagingProcessor) { + this.messagingProcessor = messagingProcessor; + this.receiptHandleGroupMap = new ConcurrentHashMap<>(); + ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); + this.renewalWorkerService = ThreadPoolMonitor.createAndMonitor( + proxyConfig.getRenewThreadPoolNums(), + proxyConfig.getRenewMaxThreadPoolNums(), + 1, TimeUnit.MINUTES, + "RenewalWorkerThread", + proxyConfig.getRenewThreadPoolQueueCapacity() + ); + this.init(); + } + + protected void init() { + this.registerConsumerListener(); + 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(); + } + }); + } + + protected void registerConsumerListener() { + this.messagingProcessor.registerConsumerListener(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() { + + } + }); + } + + protected ProxyContext createContext(String actionName) { + return ProxyContext.createForInner(this.getClass().getSimpleName() + actionName); + } + + 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(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(ReceiptHandleGroup group, String msgID, String handleStr) { + try { + group.computeIfPresent(msgID, handleStr, this::startRenewMessage); + } catch (Exception e) { + log.error("error when renew message. msgID:{}, handleStr:{}", msgID, handleStr, e); + } + } + + protected CompletableFuture startRenewMessage(MessageReceiptHandle messageReceiptHandle) { + CompletableFuture resFuture = new CompletableFuture<>(); + ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); + ProxyContext context = createContext("RenewMessage"); + ReceiptHandle handle = ReceiptHandle.decode(messageReceiptHandle.getReceiptHandleStr()); + 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 = + messagingProcessor.changeInvisibleTime(context, handle, messageReceiptHandle.getMessageId(), + messageReceiptHandle.getGroup(), messageReceiptHandle.getTopic(), RENEW_POLICY.nextDelayDuration(messageReceiptHandle.getRenewTimes())); + 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 = + messagingProcessor.getMetadataService().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 = messagingProcessor.changeInvisibleTime(context, + handle, messageReceiptHandle.getMessageId(), messageReceiptHandle.getGroup(), + messageReceiptHandle.getTopic(), retryPolicy.nextDelayDuration(messageReceiptHandle.getReconsumeTimes())); + 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 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 boolean clientIsOffline(ReceiptHandleGroupKey groupKey) { + return this.messagingProcessor.findConsumerChannel(createContext("JudgeClientOnline"), groupKey.group, groupKey.channel) == null; + } + + public void addReceiptHandle(ProxyContext ctx, Channel channel, String group, String msgID, String receiptHandle, + MessageReceiptHandle messageReceiptHandle) { + this.addReceiptHandle(ctx, new ReceiptHandleGroupKey(channel, group), msgID, receiptHandle, messageReceiptHandle); + } + + protected void addReceiptHandle(ProxyContext ctx, ReceiptHandleGroupKey key, String msgID, String receiptHandle, + MessageReceiptHandle messageReceiptHandle) { + if (key == null) { + return; + } + ConcurrentHashMapUtils.computeIfAbsent(this.receiptHandleGroupMap, key, + k -> new ReceiptHandleGroup()).put(msgID, receiptHandle, messageReceiptHandle); + } + + public MessageReceiptHandle removeReceiptHandle(ProxyContext ctx, Channel channel, String group, String msgID, String receiptHandle) { + return this.removeReceiptHandle(ctx, new ReceiptHandleGroupKey(channel, group), msgID, receiptHandle); + } + + protected MessageReceiptHandle removeReceiptHandle(ProxyContext ctx, ReceiptHandleGroupKey key, String msgID, String receiptHandle) { + if (key == null) { + return null; + } + ReceiptHandleGroup handleGroup = receiptHandleGroupMap.get(key); + if (handleGroup == null) { + return null; + } + return handleGroup.remove(msgID, receiptHandle); + } + + protected void clearGroup(ReceiptHandleGroupKey key) { + if (key == null) { + return; + } + ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); + ProxyContext context = createContext("ClearGroup"); + ReceiptHandleGroup handleGroup = receiptHandleGroupMap.remove(key); + if (handleGroup == null) { + return; + } + handleGroup.scan((msgID, handle, v) -> { + try { + handleGroup.computeIfPresent(msgID, handle, messageReceiptHandle -> { + ReceiptHandle receiptHandle = ReceiptHandle.decode(messageReceiptHandle.getReceiptHandleStr()); + messagingProcessor.changeInvisibleTime( + context, + receiptHandle, + messageReceiptHandle.getMessageId(), + messageReceiptHandle.getGroup(), + messageReceiptHandle.getTopic(), + proxyConfig.getInvisibleTimeMillisWhenClear() + ); + return CompletableFuture.completedFuture(null); + }); + } catch (Exception e) { + log.error("error when clear handle for group. key:{}", key, e); + } + }); + } + + 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"); + } + + public static 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(); + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/RequestBrokerProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/RequestBrokerProcessor.java new file mode 100644 index 00000000000..9f3187cde71 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/RequestBrokerProcessor.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/TransactionProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/TransactionProcessor.java new file mode 100644 index 00000000000..c0ba255f544 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/TransactionProcessor.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 transactionId, String messageId, String producerGroup, + TransactionStatus transactionStatus, boolean fromTransactionCheck, long timeoutMillis) { + CompletableFuture future = new CompletableFuture<>(); + try { + EndTransactionRequestData headerData = serviceManager.getTransactionService().genEndTransactionRequestHeader( + ctx, + 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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/TransactionStatus.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/TransactionStatus.java new file mode 100644 index 00000000000..e456a6061aa --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/TransactionStatus.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/ChannelExtendAttributeGetter.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/ChannelExtendAttributeGetter.java new file mode 100644 index 00000000000..3538a9496c4 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/ChannelExtendAttributeGetter.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); +} diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/Layout.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/ChannelProtocolType.java similarity index 69% rename from logging/src/main/java/org/apache/rocketmq/logging/inner/Layout.java rename to proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/ChannelProtocolType.java index 7ea3561df35..d2eeb83536d 100644 --- a/logging/src/main/java/org/apache/rocketmq/logging/inner/Layout.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/ChannelProtocolType.java @@ -15,25 +15,21 @@ * limitations under the License. */ -package org.apache.rocketmq.logging.inner; +package org.apache.rocketmq.proxy.processor.channel; -public abstract class Layout { +public enum ChannelProtocolType { + UNKNOWN("unknown"), + GRPC_V2("grpc_v2"), + GRPC_V1("grpc_v1"), + REMOTING("remoting"); - public abstract String format(LoggingEvent event); + private final String name; - public String getContentType() { - return "text/plain"; + ChannelProtocolType(String name) { + this.name = name; } - public String getHeader() { - return null; + public String getName() { + return name; } - - public String getFooter() { - return null; - } - - - abstract public boolean ignoresThrowable(); - -} \ No newline at end of file +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannel.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannel.java new file mode 100644 index 00000000000..fb9666afcc3 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannel.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannelConverter.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannelConverter.java new file mode 100644 index 00000000000..9f886e85d23 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannelConverter.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannelSerializer.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannelSerializer.java new file mode 100644 index 00000000000..a22401a5f32 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannelSerializer.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import java.util.HashMap; +import java.util.Map; +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; + +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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/validator/DefaultTopicMessageTypeValidator.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/validator/DefaultTopicMessageTypeValidator.java new file mode 100644 index 00000000000..bc2fcf30fb2 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/validator/DefaultTopicMessageTypeValidator.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 topicMessageType, TopicMessageType messageType) { + if (messageType.equals(TopicMessageType.UNSPECIFIED) || !messageType.equals(topicMessageType)) { + String errorInfo = String.format("TopicMessageType validate failed, topic type is %s, message type is %s", topicMessageType, messageType); + throw new ProxyException(ProxyExceptionCode.MESSAGE_PROPERTY_CONFLICT_WITH_TYPE, errorInfo); + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/validator/TopicMessageTypeValidator.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/validator/TopicMessageTypeValidator.java new file mode 100644 index 00000000000..137be90956d --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/validator/TopicMessageTypeValidator.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 topicMessageType Target topic + * @param messageType Message's type + */ + void validate(TopicMessageType topicMessageType, TopicMessageType messageType); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/ClientHousekeepingService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/ClientHousekeepingService.java new file mode 100644 index 00000000000..e213ae85540 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/ClientHousekeepingService.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + +} + diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/MultiProtocolRemotingServer.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/MultiProtocolRemotingServer.java new file mode 100644 index 00000000000..1142132b789 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/MultiProtocolRemotingServer.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException; +import java.security.cert.CertificateException; +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; + +/** + * 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; + private final 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 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, this.getHandshakeHandler()) + .addLast(this.getDefaultEventExecutorGroup(), + new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()), + new ProtocolNegotiationHandler(this.remotingProtocolHandler) + .addProtocolHandler(this.http2ProtocolProxyHandler) + ); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/MultiProtocolTlsHelper.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/MultiProtocolTlsHelper.java new file mode 100644 index 00000000000..59342ca3cd2 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/MultiProtocolTlsHelper.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 = null; + if (tlsTestModeEnable) { + SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); + sslContextBuilder = SslContextBuilder + .forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()) + .sslProvider(SslProvider.OPENSSL) + .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)); + + return sslContextBuilder.build(); + } + + private static ClientAuth parseClientAuthMode(String authMode) { + if (null == authMode || authMode.trim().isEmpty()) { + return ClientAuth.NONE; + } + + for (ClientAuth clientAuth : ClientAuth.values()) { + if (clientAuth.name().equals(authMode.toUpperCase())) { + return clientAuth; + } + } + + return ClientAuth.NONE; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java new file mode 100644 index 00000000000..f08094c16d0 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java @@ -0,0 +1,368 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import org.apache.rocketmq.acl.AccessValidator; +import org.apache.rocketmq.acl.plain.PlainAccessValidator; +import org.apache.rocketmq.client.exception.MQClientException; +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.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; +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.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.RequestPipeline; +import org.apache.rocketmq.remoting.ChannelEventListener; +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.TlsSystemConfig; +import org.apache.rocketmq.remoting.protocol.RemotingCommand; +import org.apache.rocketmq.remoting.protocol.RequestCode; +import org.apache.rocketmq.remoting.protocol.ResponseCode; + +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 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; + + public RemotingProtocolServer(MessagingProcessor messagingProcessor) { + this.messagingProcessor = messagingProcessor; + this.remotingChannelManager = new RemotingChannelManager(this, messagingProcessor.getProxyRelayService()); + + RequestPipeline pipeline = createRequestPipeline(); + 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.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()); + + 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.registerRemotingServer(this.defaultRemotingServer); + + 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 = Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryBuilder().setNameFormat("RemotingServerScheduler-%d").build() + ); + this.timerExecutor.scheduleAtFixedRate(this::cleanExpireRequest, 10, 10, TimeUnit.SECONDS); + } + + 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.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 { + 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 { + 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, responseFuture -> { + if (responseFuture.getResponseCommand() == null) { + future.completeExceptionally(new MQClientException("response is null after send request to client", responseFuture.getCause())); + return; + } + future.complete(responseFuture.getResponseCommand()); + }); + } catch (Throwable t) { + future.completeExceptionally(t); + } + return future; + } + + protected RequestPipeline createRequestPipeline() { + RequestPipeline pipeline = (ctx, request, context) -> { + }; + + List accessValidatorList = new ArrayList<>(); + accessValidatorList.add(new PlainAccessValidator()); + // add pipeline + // the last pipe add will execute at the first + return pipeline.pipe(new AuthenticationPipeline(accessValidatorList)); + } + + 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProxyOutClient.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProxyOutClient.java new file mode 100644 index 00000000000..5a96c41c93c --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProxyOutClient.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivity.java new file mode 100644 index 00000000000..78cd203ec45 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivity.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.HashMap; +import java.util.Map; +import java.util.Optional; +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.common.constant.LoggerName; +import org.apache.rocketmq.common.message.MessageConst; +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.common.ProxyException; +import org.apache.rocketmq.proxy.common.ProxyExceptionCode; +import org.apache.rocketmq.proxy.common.utils.ExceptionUtils; +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.processor.channel.ChannelProtocolType; +import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline; +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.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"; + 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) { + 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(ctx, request); + 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(ChannelHandlerContext ctx, RemotingCommand request) { + ProxyContext context = ProxyContext.create(); + Channel channel = ctx.channel(); + context.setAction(RemotingHelper.getRequestCodeDesc(request.getCode())) + .setProtocolType(ChannelProtocolType.REMOTING.getName()) + .setChannel(channel) + .setLocalAddress(NetworkUtil.socketAddress2String(ctx.channel().localAddress())) + .setRemoteAddress(NetworkUtil.socketAddress2String(ctx.channel().remoteAddress())); + + Optional.ofNullable(RemotingHelper.getAttributeValue(RemotingHelper.LANGUAGE_CODE_KEY, channel)) + .ifPresent(language -> context.setLanguage(language.name())); + Optional.ofNullable(RemotingHelper.getAttributeValue(RemotingHelper.CLIENT_ID_KEY, channel)) + .ifPresent(context::setClientID); + Optional.ofNullable(RemotingHelper.getAttributeValue(RemotingHelper.VERSION_KEY, channel)) + .ifPresent(version -> context.setClientVersion(MQVersion.getVersionDesc(version))); + + return context; + } + + 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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/AckMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/AckMessageActivity.java new file mode 100644 index 00000000000..723b5918bb6 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/AckMessageActivity.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/ChangeInvisibleTimeActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/ChangeInvisibleTimeActivity.java new file mode 100644 index 00000000000..9f6de99e08c --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/ChangeInvisibleTimeActivity.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/ClientManagerActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/ClientManagerActivity.java new file mode 100644 index 00000000000..69280fb8645 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/ClientManagerActivity.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.Set; +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.remoting.common.RemotingHelper; +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 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.exception.RemotingCommandException; +import org.apache.rocketmq.remoting.protocol.RemotingCommand; + +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(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(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, RemotingHelper.CLIENT_ID_KEY, clientChannelInfo.getClientId()); + RemotingHelper.setPropertyToAttr(parent, RemotingHelper.LANGUAGE_CODE_KEY, clientChannelInfo.getLanguage()); + RemotingHelper.setPropertyToAttr(parent, RemotingHelper.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(producerGroup, ctx.channel()); + ClientChannelInfo clientChannelInfo = new ClientChannelInfo( + channel, + requestHeader.getClientID(), + request.getLanguage(), + request.getVersion()); + this.messagingProcessor.unRegisterProducer(context, producerGroup, clientChannelInfo); + } + final String consumerGroup = requestHeader.getConsumerGroup(); + if (consumerGroup != null) { + RemotingChannel channel = this.remotingChannelManager.removeConsumerChannel(consumerGroup, ctx.channel()); + ClientChannelInfo clientChannelInfo = new ClientChannelInfo( + channel, + requestHeader.getClientID(), + request.getLanguage(), + request.getVersion()); + this.messagingProcessor.unRegisterConsumer(context, consumerGroup, clientChannelInfo); + } + 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(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(group, clientChannelInfo.getChannel()); + } + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/ConsumerManagerActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/ConsumerManagerActivity.java new file mode 100644 index 00000000000..e9d42afc2c9 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/ConsumerManagerActivity.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(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(GetConsumerConnectionListRequestHeader.class); + GetConsumerConnectionListRequestHeader header = (GetConsumerConnectionListRequestHeader) request.decodeCommandCustomHeader(GetConsumerConnectionListRequestHeader.class); + ConsumerGroupInfo consumerGroupInfo = messagingProcessor.getConsumerGroupInfo(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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/GetTopicRouteActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/GetTopicRouteActivity.java new file mode 100644 index 00000000000..9972c26c991 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/GetTopicRouteActivity.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.serializer.SerializerFeature; +import com.google.common.net.HostAndPort; +import io.netty.channel.ChannelHandlerContext; +import java.util.ArrayList; +import java.util.List; +import org.apache.rocketmq.common.MQVersion; +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.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; + +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(Address.AddressScheme.IPv4, 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(SerializerFeature.BrowserCompatible, + SerializerFeature.QuoteFieldNames, SerializerFeature.SkipTransientField, + SerializerFeature.MapSortField); + } else { + content = topicRouteData.encode(); + } + + response.setBody(content); + response.setCode(ResponseCode.SUCCESS); + response.setRemark(null); + return response; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/PopMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/PopMessageActivity.java new file mode 100644 index 00000000000..a635e55cc6c --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/PopMessageActivity.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/PullMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/PullMessageActivity.java new file mode 100644 index 00000000000..d548ddc0dfc --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/PullMessageActivity.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/SendMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/SendMessageActivity.java new file mode 100644 index 00000000000..17af0fdcb37 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/SendMessageActivity.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.MessageDecoder; +import org.apache.rocketmq.remoting.protocol.NamespaceUtil; +import org.apache.rocketmq.remoting.protocol.RequestCode; +import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader; +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; + +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 (ConfigurationManager.getProxyConfig().isEnableTopicMessageTypeCheck()) { + 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()); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/TransactionActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/TransactionActivity.java new file mode 100644 index 00000000000..bc5e0ca35bb --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/TransactionActivity.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.getTransactionId(), + requestHeader.getMsgId(), + requestHeader.getProducerGroup(), + transactionStatus, + requestHeader.getFromTransactionCheck() + ); + return response; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannel.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannel.java new file mode 100644 index 00000000000..40946cabf86 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannel.java @@ -0,0 +1,249 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.JSON; +import com.alibaba.fastjson.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.rocketmq.common.constant.LoggerName; +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.channel.ChannelHelper; +import org.apache.rocketmq.proxy.common.utils.FutureUtils; +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.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.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)); + } + 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); + }); + return CompletableFuture.completedFuture(null); + } catch (Throwable t) { + responseFuture.completeExceptionally(t); + return FutureUtils.completeExceptionally(t); + } + } + + @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)); + } + String errMsg = String.format("consume message directly failed, code:%s remark:%s", response.getCode(), response.getRemark()); + RuntimeException e = new RuntimeException(errMsg); + responseFuture.completeExceptionally(e); + }); + 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannelManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannelManager.java new file mode 100644 index 00000000000..133865f48bd --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannelManager.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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(Channel channel, String group, String clientId) { + return createChannel(channel, buildProducerKey(group), clientId, Collections.emptySet()); + } + + public RemotingChannel createConsumerChannel(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(String group, Channel channel) { + return removeChannel(buildProducerKey(group), channel); + } + + public RemotingChannel removeConsumerChannel(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 { + + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/common/RemotingConverter.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/common/RemotingConverter.java new file mode 100644 index 00000000000..2bd53d8de1f --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/common/RemotingConverter.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/pipeline/AuthenticationPipeline.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/pipeline/AuthenticationPipeline.java new file mode 100644 index 00000000000..4bcc1479dcc --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/pipeline/AuthenticationPipeline.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.acl.AccessResource; +import org.apache.rocketmq.acl.AccessValidator; +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.RemotingCommand; + +public class AuthenticationPipeline implements RequestPipeline { + private final List accessValidatorList; + + public AuthenticationPipeline(List accessValidatorList) { + this.accessValidatorList = accessValidatorList; + } + + @Override + public void execute(ChannelHandlerContext ctx, RemotingCommand request, ProxyContext context) throws Exception { + ProxyConfig config = ConfigurationManager.getProxyConfig(); + if (config.isEnableACL()) { + for (AccessValidator accessValidator : accessValidatorList) { + AccessResource accessResource = accessValidator.parse(request, context.getRemoteAddress()); + accessValidator.validate(accessResource); + } + } + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/AsyncNettyRequestProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/pipeline/RequestPipeline.java similarity index 66% rename from remoting/src/main/java/org/apache/rocketmq/remoting/netty/AsyncNettyRequestProcessor.java rename to proxy/src/main/java/org/apache/rocketmq/proxy/remoting/pipeline/RequestPipeline.java index db333f83383..4c46a6e7d4b 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/AsyncNettyRequestProcessor.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/pipeline/RequestPipeline.java @@ -15,15 +15,20 @@ * limitations under the License. */ -package org.apache.rocketmq.remoting.netty; +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 abstract class AsyncNettyRequestProcessor implements NettyRequestProcessor { +public interface RequestPipeline { - public void asyncProcessRequest(ChannelHandlerContext ctx, RemotingCommand request, RemotingResponseCallback responseCallback) throws Exception { - RemotingCommand response = processRequest(ctx, request); - responseCallback.callback(response); + 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); + }; } } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/ProtocolHandler.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/ProtocolHandler.java new file mode 100644 index 00000000000..4b1b03067f2 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/ProtocolHandler.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/ProtocolNegotiationHandler.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/ProtocolNegotiationHandler.java new file mode 100644 index 00000000000..da2dded5f04 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/ProtocolNegotiationHandler.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProtocolProxyHandler.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProtocolProxyHandler.java new file mode 100644 index 00000000000..913f35c93d4 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProtocolProxyHandler.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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 { + if (sslContext != null) { + ch.pipeline() + .addLast(sslContext.newHandler(ch.alloc(), LOCAL_HOST, config.getGrpcServerPort())); + } + ch.pipeline().addLast(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(); + + ctx.pipeline().addLast(new Http2ProxyFrontendHandler(outboundChannel)); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProxyBackendHandler.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProxyBackendHandler.java new file mode 100644 index 00000000000..0195b0c1c63 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProxyBackendHandler.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + + 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()); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProxyFrontendHandler.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProxyFrontendHandler.java new file mode 100644 index 00000000000..87147a32267 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/http2proxy/Http2ProxyFrontendHandler.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 Http2ProxyFrontendHandler extends ChannelInboundHandlerAdapter { + private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); + // 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; + + public Http2ProxyFrontendHandler(final Channel outboundChannel) { + this.outboundChannel = outboundChannel; + } + + @Override + public void channelRead(final ChannelHandlerContext ctx, Object msg) { + if (outboundChannel.isActive()) { + outboundChannel.writeAndFlush(msg).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture 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); + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/remoting/RemotingProtocolHandler.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/remoting/RemotingProtocolHandler.java new file mode 100644 index 00000000000..49fea89cdd3 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/protocol/remoting/RemotingProtocolHandler.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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() + ); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/ClusterServiceManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/ClusterServiceManager.java new file mode 100644 index 00000000000..d2ddfc3527b --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/ClusterServiceManager.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Executors; +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.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.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.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.client.impl.mqclient.DoNothingClientRemotingProcessor; +import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; +import org.apache.rocketmq.proxy.service.client.ProxyClientRemotingProcessor; +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; + +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 ScheduledExecutorService scheduledExecutorService; + protected MQClientAPIFactory messagingClientAPIFactory; + protected MQClientAPIFactory operationClientAPIFactory; + protected MQClientAPIFactory transactionClientAPIFactory; + + public ClusterServiceManager(RPCHook rpcHook) { + ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig(); + NameserverAccessConfig nameserverAccessConfig = new NameserverAccessConfig(proxyConfig.getNamesrvAddr(), + proxyConfig.getNamesrvDomain(), proxyConfig.getNamesrvDomainSubgroup()); + this.scheduledExecutorService = Executors.newScheduledThreadPool(3); + + this.messagingClientAPIFactory = new MQClientAPIFactory( + nameserverAccessConfig, + "ClusterMQClient_", + proxyConfig.getRocketmqMQClientNum(), + new DoNothingClientRemotingProcessor(null), + rpcHook, + scheduledExecutorService); + this.operationClientAPIFactory = new MQClientAPIFactory( + nameserverAccessConfig, + "OperationClient_", + 1, + new DoNothingClientRemotingProcessor(null), + rpcHook, + this.scheduledExecutorService + ); + + 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), + rpcHook, + scheduledExecutorService); + this.clusterTransactionService = new ClusterTransactionService(this.topicRouteService, this.producerManager, + this.transactionClientAPIFactory); + this.proxyRelayService = new ClusterProxyRelayService(this.clusterTransactionService); + + 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.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; + } + + 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); + } + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/LocalServiceManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/LocalServiceManager.java new file mode 100644 index 00000000000..4d1ca7b6699 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/LocalServiceManager.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Executors; +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.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.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 = Executors.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; + } + + 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(); + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/ServiceManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/ServiceManager.java new file mode 100644 index 00000000000..c271eca0a11 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/ServiceManager.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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(); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/ServiceManagerFactory.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/ServiceManagerFactory.java new file mode 100644 index 00000000000..c186752788d --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/ServiceManagerFactory.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.remoting.RPCHook; + +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); + } + + public static ServiceManager createForClusterMode(RPCHook rpcHook) { + return new ClusterServiceManager(rpcHook); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/admin/AdminService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/admin/AdminService.java new file mode 100644 index 00000000000..a9e6686b438 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/admin/AdminService.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/admin/DefaultAdminService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/admin/DefaultAdminService.java new file mode 100644 index 00000000000..f3c68eab5c4 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/admin/DefaultAdminService.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/ChannelManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/ChannelManager.java new file mode 100644 index 00000000000..323c8c513b3 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/ChannelManager.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/InvocationChannel.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/InvocationChannel.java new file mode 100644 index 00000000000..00e8cea99c9 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/InvocationChannel.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/InvocationContext.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/InvocationContext.java new file mode 100644 index 00000000000..9fb488eb9b1 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/InvocationContext.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/InvocationContextInterface.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/InvocationContextInterface.java new file mode 100644 index 00000000000..0db9516486b --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/InvocationContextInterface.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/SimpleChannel.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/SimpleChannel.java new file mode 100644 index 00000000000..65c1fd40665 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/SimpleChannel.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} \ No newline at end of file diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/SimpleChannelHandlerContext.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/SimpleChannelHandlerContext.java new file mode 100644 index 00000000000..801c62ee5fc --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/channel/SimpleChannelHandlerContext.java @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/client/ClusterConsumerManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/client/ClusterConsumerManager.java new file mode 100644 index 00000000000..65a4569f830 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/client/ClusterConsumerManager.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/client/ProxyClientRemotingProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/client/ProxyClientRemotingProcessor.java new file mode 100644 index 00000000000..655ce7e64dd --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/client/ProxyClientRemotingProcessor.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; + +public class ProxyClientRemotingProcessor extends ClientRemotingProcessor { + private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); + private final ProducerManager producerManager; + + public ProxyClientRemotingProcessor(ProducerManager producerManager) { + super(null); + this.producerManager = producerManager; + } + + @Override + public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) + throws RemotingCommandException { + if (request.getCode() == RequestCode.CHECK_TRANSACTION_STATE) { + return this.checkTransactionState(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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ClusterMessageService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ClusterMessageService.java new file mode 100644 index 00000000000..9f163f1b987 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ClusterMessageService.java @@ -0,0 +1,247 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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.common.ProxyException; +import org.apache.rocketmq.proxy.common.ProxyExceptionCode; +import org.apache.rocketmq.proxy.common.utils.FutureUtils; +import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; +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.PopMessageRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; +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 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 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> 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 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); + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java new file mode 100644 index 00000000000..115c140ffdc --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java @@ -0,0 +1,417 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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.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.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); + 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()); + 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); + 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); + try { + brokerController.getEndTransactionProcessor() + .processRequest(channelHandlerContext, command); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(e); + } + return future; + } + + @Override + public CompletableFuture popMessage(ProxyContext ctx, AddressableMessageQueue messageQueue, + PopMessageRequestHeader requestHeader, long timeoutMillis) { + requestHeader.setBornTime(System.currentTimeMillis()); + RemotingCommand request = LocalRemotingCommand.createRequestCommand(RequestCode.POP_MESSAGE, requestHeader); + 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) { + String key = ExtraInfoUtil.getStartOffsetInfoMapKey(messageExt.getTopic(), 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); + CompletableFuture future = new CompletableFuture<>(); + try { + RemotingCommand response = brokerController.getChangeInvisibleTimeProcessor() + .processRequest(channelHandlerContext, command); + future.complete(response); + } 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); + 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 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> 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 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"); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalRemotingCommand.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalRemotingCommand.java new file mode 100644 index 00000000000..73048dbbc24 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalRemotingCommand.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.exception.RemotingCommandException; +import org.apache.rocketmq.remoting.protocol.RemotingCommand; + +public class LocalRemotingCommand extends RemotingCommand { + + public static LocalRemotingCommand createRequestCommand(int code, CommandCustomHeader customHeader) { + LocalRemotingCommand cmd = new LocalRemotingCommand(); + cmd.setCode(code); + cmd.writeCustomHeader(customHeader); + cmd.setExtFields(new HashMap<>()); + setCmdVersion(cmd); + return cmd; + } + + @Override + public CommandCustomHeader decodeCommandCustomHeader( + Class classHeader) throws RemotingCommandException { + return classHeader.cast(readCustomHeader()); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java new file mode 100644 index 00000000000..15da1715402 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.PopMessageRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader; +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 changeInvisibleTime( + ProxyContext ctx, + ReceiptHandle handle, + String messageId, + ChangeInvisibleTimeRequestHeader requestHeader, + long timeoutMillis + ); + + CompletableFuture ackMessage( + ProxyContext ctx, + ReceiptHandle handle, + String messageId, + AckMessageRequestHeader requestHeader, + 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> 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 request(ProxyContext ctx, String brokerName, RemotingCommand request, + long timeoutMillis); + + CompletableFuture requestOneway(ProxyContext ctx, String brokerName, RemotingCommand request, + long timeoutMillis); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataService.java new file mode 100644 index 00000000000..bc9582ad816 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataService.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Optional; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +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.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +import org.apache.rocketmq.proxy.common.AbstractCacheLoader; +import org.apache.rocketmq.common.utils.AbstractStartAndShutdown; +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.TopicRouteHelper; +import org.apache.rocketmq.proxy.service.route.TopicRouteService; +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(); + + 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()) + .refreshAfterWrite(config.getTopicConfigCacheExpiredInSeconds(), TimeUnit.SECONDS) + .build(new ClusterTopicConfigCacheLoader()); + this.subscriptionGroupConfigCache = CacheBuilder.newBuilder() + .maximumSize(config.getSubscriptionGroupConfigCacheMaxNum()) + .refreshAfterWrite(config.getSubscriptionGroupConfigCacheExpiredInSeconds(), TimeUnit.SECONDS) + .build(new ClusterSubscriptionGroupConfigCacheLoader()); + + 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; + } + + 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 Optional findOneBroker(String topic) throws Exception { + try { + return topicRouteService.getAllMessageQueueView(ProxyContext.createForInner(this.getClass()), topic).getTopicRouteData().getBrokerDatas().stream().findAny(); + } catch (Exception e) { + if (TopicRouteHelper.isTopicNotExistError(e)) { + return Optional.empty(); + } + throw e; + } + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ForwardRequestProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/LocalMetadataService.java similarity index 51% rename from broker/src/main/java/org/apache/rocketmq/broker/processor/ForwardRequestProcessor.java rename to proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/LocalMetadataService.java index cd935983465..7f3c041f259 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ForwardRequestProcessor.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/LocalMetadataService.java @@ -14,33 +14,33 @@ * 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.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.remoting.netty.AsyncNettyRequestProcessor; -import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; -import org.apache.rocketmq.remoting.protocol.RemotingCommand; +package org.apache.rocketmq.proxy.service.metadata; -public class ForwardRequestProcessor extends AsyncNettyRequestProcessor implements NettyRequestProcessor { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); +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 ForwardRequestProcessor(final BrokerController brokerController) { + public LocalMetadataService(BrokerController brokerController) { this.brokerController = brokerController; } @Override - public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) { - return null; + public TopicMessageType getTopicMessageType(ProxyContext ctx, String topic) { + TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(topic); + if (topicConfig == null) { + return TopicMessageType.UNSPECIFIED; + } + return topicConfig.getTopicMessageType(); } @Override - public boolean rejectRequest() { - return false; + public SubscriptionGroupConfig getSubscriptionGroupConfig(ProxyContext ctx, String group) { + return this.brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().get(group); } } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/MetadataService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/MetadataService.java new file mode 100644 index 00000000000..3ee0f3eacd3 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/MetadataService.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/AbstractProxyRelayService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/AbstractProxyRelayService.java new file mode 100644 index 00000000000..08f00bd83c0 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/AbstractProxyRelayService.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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), + 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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ClusterProxyRelayService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ClusterProxyRelayService.java new file mode 100644 index 00000000000..71ce222a8c0 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ClusterProxyRelayService.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/LocalProxyRelayService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/LocalProxyRelayService.java new file mode 100644 index 00000000000..9fcc27fc53d --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/LocalProxyRelayService.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ProxyChannel.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ProxyChannel.java new file mode 100644 index 00000000000..5a1185a81e8 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ProxyChannel.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +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; + } + 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 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ProxyRelayResult.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ProxyRelayResult.java new file mode 100644 index 00000000000..95b98d4d6b1 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ProxyRelayResult.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ProxyRelayService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ProxyRelayService.java new file mode 100644 index 00000000000..96d3b699578 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/ProxyRelayService.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + ); +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/RelayData.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/RelayData.java new file mode 100644 index 00000000000..20ee0f5fdfa --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/relay/RelayData.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/AddressableMessageQueue.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/AddressableMessageQueue.java new file mode 100644 index 00000000000..ca877f3278f --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/AddressableMessageQueue.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Objects; +import org.apache.rocketmq.common.message.MessageQueue; + +public class AddressableMessageQueue implements Comparable { + + private final MessageQueue messageQueue; + private final String brokerAddr; + + public AddressableMessageQueue(MessageQueue messageQueue, String brokerAddr) { + this.messageQueue = messageQueue; + this.brokerAddr = brokerAddr; + } + + @Override + public int compareTo(AddressableMessageQueue o) { + return messageQueue.compareTo(o.messageQueue); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof AddressableMessageQueue)) { + return false; + } + AddressableMessageQueue queue = (AddressableMessageQueue) o; + return Objects.equals(messageQueue, queue.messageQueue); + } + + @Override + public int hashCode() { + return messageQueue == null ? 1 : messageQueue.hashCode(); + } + + public int getQueueId() { + return this.messageQueue.getQueueId(); + } + + public String getBrokerName() { + return this.messageQueue.getBrokerName(); + } + + public String getTopic() { + return messageQueue.getTopic(); + } + + public MessageQueue getMessageQueue() { + return messageQueue; + } + + public String getBrokerAddr() { + return brokerAddr; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("messageQueue", messageQueue) + .add("brokerAddr", brokerAddr) + .toString(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java new file mode 100644 index 00000000000..84252f8b8e7 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.common.message.MessageQueue; +import org.apache.rocketmq.proxy.common.Address; +import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; +import org.apache.rocketmq.proxy.common.ProxyContext; +import org.apache.rocketmq.remoting.protocol.route.BrokerData; +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(); + + ProxyTopicRouteData proxyTopicRouteData = new ProxyTopicRouteData(); + proxyTopicRouteData.setQueueDatas(topicRouteData.getQueueDatas()); + + 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); + } + proxyTopicRouteData.getBrokerDatas().add(proxyBrokerData); + } + + return proxyTopicRouteData; + } + + @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); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/LocalTopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/LocalTopicRouteService.java new file mode 100644 index 00000000000..d67b68f38e9 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/LocalTopicRouteService.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.HashMap; +import java.util.List; +import org.apache.rocketmq.broker.BrokerController; +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.client.impl.mqclient.MQClientAPIFactory; +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)); + } + + @Override + public ProxyTopicRouteData getTopicRouteForProxy(ProxyContext ctx, List
    requestHostAndPortList, + String topicName) throws Exception { + MessageQueueView messageQueueView = getAllMessageQueueView(ctx, topicName); + TopicRouteData topicRouteData = messageQueueView.getTopicRouteData(); + + ProxyTopicRouteData proxyTopicRouteData = new ProxyTopicRouteData(); + proxyTopicRouteData.setQueueDatas(topicRouteData.getQueueDatas()); + + 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()) { + String brokerAddr = brokerData.getBrokerAddrs().get(brokerId); + HostAndPort brokerHostAndPort = HostAndPort.fromString(brokerAddr); + HostAndPort grpcHostAndPort = HostAndPort.fromParts(brokerHostAndPort.getHost(), grpcPort); + + proxyBrokerData.getBrokerAddrs().put(brokerId, Lists.newArrayList(new Address(Address.AddressScheme.IPv4, grpcHostAndPort))); + } + proxyTopicRouteData.getBrokerDatas().add(proxyBrokerData); + } + + return proxyTopicRouteData; + } + + @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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelector.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelector.java new file mode 100644 index 00000000000..85cd18d45c9 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelector.java @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.lang3.StringUtils; +import org.apache.rocketmq.common.constant.PermName; +import org.apache.rocketmq.common.message.MessageQueue; +import org.apache.rocketmq.remoting.protocol.route.QueueData; + +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; + + public MessageQueueSelector(TopicRouteWrapper topicRouteWrapper, boolean read) { + 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()); + } + + 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.getMessageQueue().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 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; + } + + @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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueView.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueView.java new file mode 100644 index 00000000000..fe5387cfd7e --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueView.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.remoting.protocol.route.TopicRouteData; + +public class MessageQueueView { + public static final MessageQueueView WRAPPED_EMPTY_QUEUE = new MessageQueueView("", new TopicRouteData()); + + private final MessageQueueSelector readSelector; + private final MessageQueueSelector writeSelector; + private final TopicRouteWrapper topicRouteWrapper; + + public MessageQueueView(String topic, TopicRouteData topicRouteData) { + this.topicRouteWrapper = new TopicRouteWrapper(topicRouteData, topic); + + this.readSelector = new MessageQueueSelector(topicRouteWrapper, true); + this.writeSelector = new MessageQueueSelector(topicRouteWrapper, false); + } + + 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ProxyTopicRouteData.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ProxyTopicRouteData.java new file mode 100644 index 00000000000..da8b3f61127 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ProxyTopicRouteData.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteHelper.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteHelper.java new file mode 100644 index 00000000000..651010ce178 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteHelper.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java new file mode 100644 index 00000000000..3fa6414c39b --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.time.Duration; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import org.apache.rocketmq.client.exception.MQClientException; +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.thread.ThreadPoolMonitor; +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.common.utils.AbstractStartAndShutdown; +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.client.impl.mqclient.MQClientAPIFactory; +import org.apache.rocketmq.remoting.protocol.ResponseCode; +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 MQClientAPIFactory mqClientAPIFactory; + + protected final LoadingCache topicCache; + protected final ScheduledExecutorService scheduledExecutorService; + protected final ThreadPoolExecutor cacheRefreshExecutor; + private final TopicRouteCacheLoader topicRouteCacheLoader = new TopicRouteCacheLoader(); + + + public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { + ProxyConfig config = ConfigurationManager.getProxyConfig(); + + this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryImpl("TopicRouteService_") + ); + this.cacheRefreshExecutor = ThreadPoolMonitor.createAndMonitor( + config.getTopicRouteServiceThreadPoolNums(), + config.getTopicRouteServiceThreadPoolNums(), + 1000 * 60, + TimeUnit.MILLISECONDS, + "TopicRouteCacheRefresh", + config.getTopicRouteServiceThreadPoolQueueCapacity() + ); + this.mqClientAPIFactory = mqClientAPIFactory; + + this.topicCache = Caffeine.newBuilder().maximumSize(config.getTopicRouteServiceCacheMaxNum()). + refreshAfterWrite(config.getTopicRouteServiceCacheExpiredInSeconds(), TimeUnit.SECONDS). + executor(cacheRefreshExecutor).build(new CacheLoader() { + @Override public @Nullable MessageQueueView load(String topic) throws Exception { + try { + TopicRouteData topicRouteData = topicRouteCacheLoader.loadTopicRouteData(topic); + if (isTopicRouteValid(topicRouteData)) { + MessageQueueView tmp = new MessageQueueView(topic, topicRouteData); + log.info("load topic route from namesrv. topic: {}, queue: {}", topic, tmp); + return tmp; + } + return MessageQueueView.WRAPPED_EMPTY_QUEUE; + } 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; + } + } + }); + + this.init(); + } + + protected void init() { + this.appendShutdown(this.scheduledExecutorService::shutdown); + this.appendStartAndShutdown(this.mqClientAPIFactory); + } + + 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 abstract class AbstractTopicRouteCacheLoader extends AbstractCacheLoader { + + public AbstractTopicRouteCacheLoader() { + super(cacheRefreshExecutor); + } + + protected abstract TopicRouteData loadTopicRouteData(String topic) throws Exception; + + @Override + public MessageQueueView getDirectly(String topic) throws Exception { + try { + TopicRouteData topicRouteData = loadTopicRouteData(topic); + + if (isTopicRouteValid(topicRouteData)) { + MessageQueueView tmp = new MessageQueueView(topic, topicRouteData); + log.info("load topic route from namesrv. topic: {}, queue: {}", topic, tmp); + return tmp; + } + return MessageQueueView.WRAPPED_EMPTY_QUEUE; + } catch (Exception e) { + if (TopicRouteHelper.isTopicNotExistError(e)) { + return MessageQueueView.WRAPPED_EMPTY_QUEUE; + } + throw e; + } + } + + @Override + protected void onErr(String key, Exception e) { + log.error("load topic route from namesrv failed. topic:{}", key, e); + } + } + + protected class TopicRouteCacheLoader extends AbstractTopicRouteCacheLoader { + + @Override + protected TopicRouteData loadTopicRouteData(String topic) throws Exception { + return mqClientAPIFactory.getClient().getTopicRouteInfoFromNameServer(topic, Duration.ofSeconds(3).toMillis()); + } + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteWrapper.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteWrapper.java new file mode 100644 index 00000000000..7956c6284ea --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteWrapper.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/AbstractSystemMessageSyncer.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/AbstractSystemMessageSyncer.java new file mode 100644 index 00000000000..fcdc25cacda --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/AbstractSystemMessageSyncer.java @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.JSON; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +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.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.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.common.utils.StartAndShutdown; +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.client.impl.mqclient.MQClientAPIFactory; +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; + +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(null, 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() { + if (this.adminService.topicExist(this.getBroadcastTopicName())) { + return; + } + + 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncer.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncer.java new file mode 100644 index 00000000000..f70c06b8f46 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncer.java @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.JSON; +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; +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.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.client.impl.mqclient.MQClientAPIFactory; +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; + +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 s, 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(clientChannelInfo.getChannel().id().asLongText()); + } + } + } + + @Override + public void shutdown() { + + } + }); + } + + @Override + public void shutdown() throws Exception { + this.threadPoolExecutor.shutdown(); + super.shutdown(); + } + + 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(data.getGroup() + "@" + decodedChannel.id().asLongText(), 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerData.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerData.java new file mode 100644 index 00000000000..97760506f14 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerData.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatType.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatType.java new file mode 100644 index 00000000000..8f0801f54d6 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatType.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/AbstractTransactionService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/AbstractTransactionService.java new file mode 100644 index 00000000000..f0e083adead --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/AbstractTransactionService.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 producerGroup, long tranStateTableOffset, long commitLogOffset, String transactionId, + Message message) { + return this.addTransactionDataByBrokerName(ctx, this.getBrokerNameByAddr(brokerAddr), producerGroup, tranStateTableOffset, commitLogOffset, transactionId, message); + } + + @Override + public TransactionData addTransactionDataByBrokerName(ProxyContext ctx, String brokerName, String producerGroup, long tranStateTableOffset, long commitLogOffset, String transactionId, + Message message) { + if (StringUtils.isBlank(brokerName)) { + return null; + } + TransactionData transactionData = new TransactionData( + brokerName, + tranStateTableOffset, commitLogOffset, transactionId, + System.currentTimeMillis(), + ConfigurationManager.getProxyConfig().getTransactionDataExpireMillis()); + + this.transactionDataManager.addTransactionData( + producerGroup, + transactionId, + transactionData + ); + return transactionData; + } + + @Override + public EndTransactionRequestData genEndTransactionRequestHeader(ProxyContext ctx, 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.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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/ClusterTransactionService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/ClusterTransactionService.java new file mode 100644 index 00000000000..1ec42864636 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/ClusterTransactionService.java @@ -0,0 +1,301 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/EndTransactionRequestData.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/EndTransactionRequestData.java new file mode 100644 index 00000000000..dbf247640ef --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/EndTransactionRequestData.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/LocalTransactionService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/LocalTransactionService.java new file mode 100644 index 00000000000..4a27e4ff24a --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/LocalTransactionService.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/TransactionData.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/TransactionData.java new file mode 100644 index 00000000000..88fbf44396e --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/TransactionData.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 long tranStateTableOffset; + private final long commitLogOffset; + private final String transactionId; + private final long checkTimestamp; + private final long expireMs; + + public TransactionData(String brokerName, long tranStateTableOffset, long commitLogOffset, String transactionId, + long checkTimestamp, long expireMs) { + this.brokerName = brokerName; + this.tranStateTableOffset = tranStateTableOffset; + this.commitLogOffset = commitLogOffset; + this.transactionId = transactionId; + this.checkTimestamp = checkTimestamp; + this.expireMs = expireMs; + } + + public String getBrokerName() { + return brokerName; + } + + 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/TransactionDataManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/TransactionDataManager.java new file mode 100644 index 00000000000..81cd26ee9d3 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/TransactionDataManager.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/TransactionService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/TransactionService.java new file mode 100644 index 00000000000..a7ab3532424 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/transaction/TransactionService.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 producerGroup, long tranStateTableOffset, long commitLogOffset, String transactionId, + Message message); + + TransactionData addTransactionDataByBrokerName(ProxyContext ctx, String brokerName, String producerGroup, long tranStateTableOffset, long commitLogOffset, String transactionId, + Message message); + + EndTransactionRequestData genEndTransactionRequestHeader(ProxyContext ctx, String producerGroup, Integer commitOrRollback, + boolean fromTransactionCheck, String msgId, String transactionId); + + void onSendCheckTransactionStateFailed(ProxyContext context, String producerGroup, TransactionData transactionData); +} diff --git a/proxy/src/main/resources/rmq.proxy.logback.xml b/proxy/src/main/resources/rmq.proxy.logback.xml new file mode 100644 index 00000000000..d38827f92d8 --- /dev/null +++ b/proxy/src/main/resources/rmq.proxy.logback.xml @@ -0,0 +1,428 @@ + + + + + + + ${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 + + + + + + + + + + %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/ProxyStartupTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/ProxyStartupTest.java new file mode 100644 index 00000000000..58213df4adf --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/ProxyStartupTest.java @@ -0,0 +1,295 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/common/ReceiptHandleGroupTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/common/ReceiptHandleGroupTest.java new file mode 100644 index 00000000000..93abae324cd --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/common/ReceiptHandleGroupTest.java @@ -0,0 +1,256 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.proxy.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 testGetWhenComputeIfPresent() { + String handle1 = createHandle(); + String handle2 = createHandle(); + AtomicReference getHandleRef = new AtomicReference<>(); + + receiptHandleGroup.put(msgID, handle1, 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, handle1, 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, handle1, 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, handle1, 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, handle1, 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()); + } + + private MessageReceiptHandle createMessageReceiptHandle(String handle, String msgID) { + return new MessageReceiptHandle(GROUP, TOPIC, 0, handle, msgID, 0, 0); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/common/RenewStrategyPolicyTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/common/RenewStrategyPolicyTest.java new file mode 100644 index 00000000000..54e6272749a --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/common/RenewStrategyPolicyTest.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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() { + } + +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/common/utils/FilterUtilTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/common/utils/FilterUtilTest.java new file mode 100644 index 00000000000..23389e9d3b7 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/common/utils/FilterUtilTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } + +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/config/ConfigurationManagerTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/config/ConfigurationManagerTest.java new file mode 100644 index 00000000000..74803609ba5 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/config/ConfigurationManagerTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +public class ConfigurationManagerTest extends InitConfigTest { + + @Test + public void testIntConfig() { + 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(); + } + +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/config/InitConfigTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/config/InitConfigTest.java new file mode 100644 index 00000000000..d71d163ac69 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/config/InitConfigTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.intConfig(); + } + + @After + public void after() { + System.clearProperty(RMQ_PROXY_HOME); + } +} \ No newline at end of file diff --git a/logging/src/test/java/org/apache/rocketmq/logging/inner/MessageFormatterTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/config/MetricCollectorModeTest.java similarity index 54% rename from logging/src/test/java/org/apache/rocketmq/logging/inner/MessageFormatterTest.java rename to proxy/src/test/java/org/apache/rocketmq/proxy/config/MetricCollectorModeTest.java index 5fa80ad56e8..60c754e607c 100644 --- a/logging/src/test/java/org/apache/rocketmq/logging/inner/MessageFormatterTest.java +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/config/MetricCollectorModeTest.java @@ -15,26 +15,22 @@ * limitations under the License. */ -package org.apache.rocketmq.logging.inner; +package org.apache.rocketmq.proxy.config; - -import org.apache.rocketmq.logging.InnerLoggerFactory; import org.junit.Assert; import org.junit.Test; -public class MessageFormatterTest { +public class MetricCollectorModeTest { @Test - public void formatTest(){ - InnerLoggerFactory.FormattingTuple logging = InnerLoggerFactory.MessageFormatter.format("this is {},and {}", "logging", 6546); - String message = logging.getMessage(); - Assert.assertTrue(message.contains("logging")); + public void testGetEnumByOrdinal() { + Assert.assertEquals(MetricCollectorMode.OFF, MetricCollectorMode.getEnumByString("off")); + Assert.assertEquals(MetricCollectorMode.ON, MetricCollectorMode.getEnumByString("on")); + Assert.assertEquals(MetricCollectorMode.PROXY, MetricCollectorMode.getEnumByString("proxy")); - InnerLoggerFactory.FormattingTuple format = InnerLoggerFactory.MessageFormatter.format("cause exception {}", 143545, new RuntimeException()); - String message1 = format.getMessage(); - Throwable throwable = format.getThrowable(); - System.out.println(message1); - Assert.assertTrue(throwable != null); + Assert.assertEquals(MetricCollectorMode.OFF, MetricCollectorMode.getEnumByString("OFF")); + Assert.assertEquals(MetricCollectorMode.ON, MetricCollectorMode.getEnumByString("ON")); + Assert.assertEquals(MetricCollectorMode.PROXY, MetricCollectorMode.getEnumByString("PROXY")); } -} +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/AbstractMessingActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/AbstractMessingActivityTest.java new file mode 100644 index 00000000000..3c2967357f0 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/AbstractMessingActivityTest.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 AbstractMessingActivityTest extends InitConfigTest { + + public static class MockMessingActivity extends AbstractMessingActivity { + + public MockMessingActivity(MessagingProcessor messagingProcessor, + GrpcClientSettingsManager grpcClientSettingsManager, + GrpcChannelManager grpcChannelManager) { + super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); + } + } + + private AbstractMessingActivity messingActivity; + + @Before + public void before() throws Throwable { + super.before(); + this.messingActivity = new MockMessingActivity(null, null, null); + } + + @Test + public void testValidateTopic() { + assertThrows(GrpcProxyException.class, () -> messingActivity.validateTopic(Resource.newBuilder().build())); + assertThrows(GrpcProxyException.class, () -> messingActivity.validateTopic(Resource.newBuilder().setName(TopicValidator.RMQ_SYS_TRACE_TOPIC).build())); + assertThrows(GrpcProxyException.class, () -> messingActivity.validateTopic(Resource.newBuilder().setName("@").build())); + assertThrows(GrpcProxyException.class, () -> messingActivity.validateTopic(Resource.newBuilder().setName(createString(128)).build())); + messingActivity.validateTopic(Resource.newBuilder().setName(createString(127)).build()); + } + + @Test + public void testValidateConsumer() { + assertThrows(GrpcProxyException.class, () -> messingActivity.validateConsumerGroup(Resource.newBuilder().build())); + assertThrows(GrpcProxyException.class, () -> messingActivity.validateConsumerGroup(Resource.newBuilder().setName(MixAll.CID_SYS_RMQ_TRANS).build())); + assertThrows(GrpcProxyException.class, () -> messingActivity.validateConsumerGroup(Resource.newBuilder().setName("@").build())); + assertThrows(GrpcProxyException.class, () -> messingActivity.validateConsumerGroup(Resource.newBuilder().setName(createString(256)).build())); + messingActivity.validateConsumerGroup(Resource.newBuilder().setName(createString(255)).build()); + } + + private static String createString(int len) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) { + sb.append('a'); + } + return sb.toString(); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/BaseActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/BaseActivityTest.java new file mode 100644 index 00000000000..524945bd6fe --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/BaseActivityTest.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.proxy.grpc.interceptor.InterceptorConstants; +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(InterceptorConstants.CLIENT_ID, CLIENT_ID); + metadata.put(InterceptorConstants.LANGUAGE, JAVA); + metadata.put(InterceptorConstants.REMOTE_ADDRESS, REMOTE_ADDR); + metadata.put(InterceptorConstants.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) + ); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplicationTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplicationTest.java new file mode 100644 index 00000000000..7ba03fdbf1f --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplicationTest.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.proxy.common.ProxyContext; +import org.apache.rocketmq.proxy.config.InitConfigTest; +import org.apache.rocketmq.proxy.grpc.interceptor.InterceptorConstants; +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 + GrpcMessingActivity grpcMessingActivity; + 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(); + grpcMessagingApplication = new GrpcMessagingApplication(grpcMessingActivity); + } + + @Test + public void testQueryRoute() { + Metadata metadata = new Metadata(); + metadata.put(InterceptorConstants.CLIENT_ID, CLIENT_ID); + metadata.put(InterceptorConstants.LANGUAGE, JAVA); + metadata.put(InterceptorConstants.REMOTE_ADDRESS, REMOTE_ADDR); + metadata.put(InterceptorConstants.LOCAL_ADDRESS, LOCAL_ADDR); + + Assert.assertNotNull(Context.current() + .withValue(InterceptorConstants.METADATA, metadata) + .attach()); + + CompletableFuture future = new CompletableFuture<>(); + QueryRouteRequest request = QueryRouteRequest.newBuilder() + .setEndpoints(grpcEndpoints) + .setTopic(Resource.newBuilder().setName(TOPIC).build()) + .build(); + Mockito.when(grpcMessingActivity.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(InterceptorConstants.LANGUAGE, JAVA); + metadata.put(InterceptorConstants.REMOTE_ADDRESS, REMOTE_ADDR); + metadata.put(InterceptorConstants.LOCAL_ADDRESS, LOCAL_ADDR); + + Assert.assertNotNull(Context.current() + .withValue(InterceptorConstants.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()); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcClientChannelTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcClientChannelTest.java new file mode 100644 index 00000000000..1bdbdd9befe --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcClientChannelTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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))); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/client/ClientActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/client/ClientActivityTest.java new file mode 100644 index 00000000000..a5d4e3c9193 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/client/ClientActivityTest.java @@ -0,0 +1,430 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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.proxy.common.ProxyContext; +import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest; +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.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.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.anyString; +import static org.mockito.Mockito.doNothing; +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(); + StreamObserver streamObserver = clientActivity.telemetry(context, new StreamObserver() { + @Override + public void onNext(TelemetryCommand value) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onCompleted() { + } + }); + streamObserver.onNext(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(); + StreamObserver streamObserver = clientActivity.telemetry(context, new StreamObserver() { + @Override + public void onNext(TelemetryCommand value) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onCompleted() { + } + }); + streamObserver.onNext(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() { + + } + }; + StreamObserver requestObserver = this.clientActivity.telemetry( + ctx, + responseObserver + ); + requestObserver.onNext(TelemetryCommand.newBuilder() + .setSettings(settings) + .build()); + return future; + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManagerTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManagerTest.java new file mode 100644 index 00000000000..9044873a6d3 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManagerTest.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 com.google.protobuf.util.Durations; +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.Mockito.when; + +public class GrpcClientSettingsManagerTest extends BaseActivityTest { + private GrpcClientSettingsManager grpcClientSettingsManager; + + @Before + public void before() throws Throwable { + super.before(); + this.grpcClientSettingsManager = new GrpcClientSettingsManager(this.messagingProcessor); + } + + @Test + public void testGetProducerData() { + ProxyContext context = ProxyContext.create().withVal(ContextVariable.CLIENT_ID, CLIENT_ID); + + this.grpcClientSettingsManager.updateClientSettings(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() { + SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); + when(this.messagingProcessor.getSubscriptionGroupConfig(any(), any())) + .thenReturn(subscriptionGroupConfig); + + this.grpcClientSettingsManager.updateClientSettings(CLIENT_ID, Settings.newBuilder() + .setSubscription(Subscription.newBuilder() + .setGroup(Resource.newBuilder().setName("group").build()) + .build()) + .build()); + + ProxyContext context = ProxyContext.create().withVal(ContextVariable.CLIENT_ID, CLIENT_ID); + + 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)); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcConverterTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcConverterTest.java new file mode 100644 index 00000000000..bc9b8a60b40 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcConverterTest.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.apache.rocketmq.common.message.MessageExt; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +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); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcValidatorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcValidatorTest.java new file mode 100644 index 00000000000..df42844e95e --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcValidatorTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertThrows; + +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"); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivityTest.java new file mode 100644 index 00000000000..4df834bb651 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivityTest.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Code; +import apache.rocketmq.v2.Resource; +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.grpc.v2.BaseActivityTest; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +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, receiptHandleProcessor, grpcClientSettingsManager, grpcChannelManager); + } + + @Test + public void testAckMessage() throws Throwable { + when(this.messagingProcessor.ackMessage(any(), any(), eq("msg1"), anyString(), anyString())) + .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())) + .thenReturn(CompletableFuture.completedFuture(msg2AckResult)); + + AckResult msg3AckResult = new AckResult(); + msg3AckResult.setStatus(AckStatus.NO_EXIST); + when(this.messagingProcessor.ackMessage(any(), any(), eq("msg3"), anyString(), anyString())) + .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()) + .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()); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ChangeInvisibleDurationActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ChangeInvisibleDurationActivityTest.java new file mode 100644 index 00000000000..fdd052da764 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ChangeInvisibleDurationActivityTest.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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, receiptHandleProcessor, + 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(receiptHandleProcessor.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()); + } + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivityTest.java new file mode 100644 index 00000000000..e5aeb025d9e --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivityTest.java @@ -0,0 +1,316 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; +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.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.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +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, receiptHandleProcessor, + 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(), 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 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(), + 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() { + 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); + 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()); + } + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageResponseStreamWriterTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageResponseStreamWriterTest.java new file mode 100644 index 00000000000..fb449a89989 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageResponseStreamWriterTest.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +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()); + + 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); + 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 + ); + + verify(streamObserver, times(1)).onCompleted(); + verify(streamObserver, times(4)).onNext(any()); + verify(this.messagingProcessor, times(1)) + .changeInvisibleTime(any(), any(), anyString(), anyString(), anyString(), anyLong()); + + 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()); + } + + @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()); + } + + 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; + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/ForwardMessageToDLQActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/ForwardMessageToDLQActivityTest.java new file mode 100644 index 00000000000..ec620340c57 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/ForwardMessageToDLQActivityTest.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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,receiptHandleProcessor, grpcClientSettingsManager, grpcChannelManager); + } + + @Test + public void testForwardMessageToDeadLetterQueue() throws Throwable { + ArgumentCaptor receiptHandleCaptor = ArgumentCaptor.forClass(ReceiptHandle.class); + when(this.messagingProcessor.forwardMessageToDeadLetterQueue(any(), receiptHandleCaptor.capture(), anyString(), anyString(), anyString())) + .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())) + .thenReturn(CompletableFuture.completedFuture(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""))); + + String savedHandleStr = buildReceiptHandle("topic", System.currentTimeMillis(),3000); + when(receiptHandleProcessor.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()); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivityTest.java new file mode 100644 index 00000000000..588423bb93f --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivityTest.java @@ -0,0 +1,853 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +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.when; + +public class SendMessageActivityTest 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 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 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 testSendOrderMessageQueueSelector() { + 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); + SendMessageActivity.SendMessageQueueSelector selector1 = new SendMessageActivity.SendMessageQueueSelector( + SendMessageRequest.newBuilder() + .addMessages(Message.newBuilder() + .setSystemProperties(SystemProperties.newBuilder() + .setMessageGroup(String.valueOf(1)) + .build()) + .build()) + .build() + ); + + 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)); + + MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData); + SendMessageActivity.SendMessageQueueSelector selector = new SendMessageActivity.SendMessageQueueSelector( + SendMessageRequest.newBuilder() + .addMessages(Message.newBuilder().build()) + .build() + ); + + 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 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(); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/route/RouteActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/route/RouteActivityTest.java new file mode 100644 index 00000000000..a7ba69098bc --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/route/RouteActivityTest.java @@ -0,0 +1,285 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } + + 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; + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/transaction/EndTransactionActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/transaction/EndTransactionActivityTest.java new file mode 100644 index 00000000000..07a3abb9937 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/transaction/EndTransactionActivityTest.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(), + 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); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/BaseProcessorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/BaseProcessorTest.java new file mode 100644 index 00000000000..5c1ea9627e6 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/BaseProcessorTest.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + @Mock + protected ProducerProcessor producerProcessor; + @Mock + protected ConsumerProcessor consumerProcessor; + @Mock + protected TransactionProcessor transactionProcessor; + @Mock + protected ClientProcessor clientProcessor; + + 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) { + 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(RANDOM.nextInt(Integer.MAX_VALUE), System.currentTimeMillis(), invisibleTime, + RANDOM.nextInt(Integer.MAX_VALUE), topic, "mockBroker", RANDOM.nextInt(Integer.MAX_VALUE), RANDOM.nextInt(Integer.MAX_VALUE))); + 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()); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ConsumerProcessorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ConsumerProcessorTest.java new file mode 100644 index 00000000000..876b25b30b2 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ConsumerProcessorTest.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.HashSet; +import java.util.List; +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.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.utils.ProxyUtils; +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 static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +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 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(); + ReceiptHandleProcessor receiptHandleProcessor = new ReceiptHandleProcessor(messagingProcessor); + 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(), anyLong())) + .thenReturn(CompletableFuture.completedFuture(mock(AckResult.class))); + + ArgumentCaptor toDLQMessageIdArgumentCaptor = ArgumentCaptor.forClass(String.class); + when(this.messagingProcessor.forwardMessageToDeadLetterQueue(any(), any(), toDLQMessageIdArgumentCaptor.capture(), anyString(), anyString(), 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; + }, + 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, 3000).get(); + + assertEquals(AckStatus.OK, ackResult.getStatus()); + assertEquals(KeyBuilder.buildPopRetryTopic(TOPIC, CONSUMER_GROUP), requestHeaderArgumentCaptor.getValue().getTopic()); + assertEquals(CONSUMER_GROUP, requestHeaderArgumentCaptor.getValue().getConsumerGroup()); + assertEquals(handle.getReceiptHandle(), requestHeaderArgumentCaptor.getValue().getExtraInfo()); + } + + @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, 3000).get(); + + assertEquals(AckStatus.OK, ackResult.getStatus()); + assertEquals(KeyBuilder.buildPopRetryTopic(TOPIC, CONSUMER_GROUP), 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)); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ProducerProcessorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ProducerProcessorTest.java new file mode 100644 index 00000000000..de63b7e75f8 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ProducerProcessorTest.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Executors; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.client.producer.SendStatus; +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.sysflag.MessageSysFlag; +import org.apache.rocketmq.common.utils.NetworkUtil; +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.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +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.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(), + 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(), + 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), "", 16, 3000); + RemotingCommand remotingCommand = this.producerProcessor.forwardMessageToDeadLetterQueue( + createContext(), + create(messageExt), + messageExt.getMsgId(), + CONSUMER_GROUP, + TOPIC, + 3000 + ).get(); + + assertNotNull(remotingCommand); + ConsumerSendMsgBackRequestHeader requestHeader = requestHeaderArgumentCaptor.getValue(); + assertEquals(messageExt.getTopic(), requestHeader.getOriginTopic()); + assertEquals(messageExt.getMsgId(), requestHeader.getOriginMsgId()); + assertEquals(CONSUMER_GROUP, requestHeader.getGroup()); + } + + 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); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ReceiptHandleProcessorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ReceiptHandleProcessorTest.java new file mode 100644 index 00000000000..7206e6b791a --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ReceiptHandleProcessorTest.java @@ -0,0 +1,649 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.buffer.ByteBufAllocator; +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.ChannelPipeline; +import io.netty.channel.ChannelProgressivePromise; +import io.netty.channel.ChannelPromise; +import io.netty.channel.EventLoop; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import java.net.SocketAddress; +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.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.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.RenewStrategyPolicy; +import org.apache.rocketmq.proxy.common.ProxyExceptionCode; +import org.apache.rocketmq.proxy.common.ReceiptHandleGroup; +import org.apache.rocketmq.proxy.config.ConfigurationManager; +import org.apache.rocketmq.proxy.config.ProxyConfig; +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.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 ReceiptHandleProcessorTest extends BaseProcessorTest { + private ReceiptHandleProcessor receiptHandleProcessor; + + 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() { + 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 MockChannel()); + receiptHandleProcessor = new ReceiptHandleProcessor(messagingProcessor); + Mockito.doNothing().when(messagingProcessor).registerConsumerListener(Mockito.any(ConsumerIdsChangeListener.class)); + messageReceiptHandle = new MessageReceiptHandle(GROUP, TOPIC, QUEUE_ID, receiptHandle, MESSAGE_ID, OFFSET, + RECONSUME_TIMES); + } + + @Test + public void testAddReceiptHandle() { + Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); + receiptHandleProcessor.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, receiptHandle, messageReceiptHandle); + Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(new SubscriptionGroupConfig()); + Mockito.when(messagingProcessor.findConsumerChannel(Mockito.any(), Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); + receiptHandleProcessor.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 testRenewReceiptHandle() { + ProxyConfig config = ConfigurationManager.getProxyConfig(); + Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); + receiptHandleProcessor.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, receiptHandle, messageReceiptHandle); + SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); + Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(groupConfig); + Mockito.when(messagingProcessor.findConsumerChannel(Mockito.any(), 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)); + receiptHandleProcessor.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()))); + receiptHandleProcessor.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()))); + receiptHandleProcessor.scheduleRenewTask(); + } + + @Test + public void testRenewExceedMaxRenewTimes() { + ProxyConfig config = ConfigurationManager.getProxyConfig(); + Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); + Mockito.when(messagingProcessor.findConsumerChannel(Mockito.any(), Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); + receiptHandleProcessor.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, receiptHandle, 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(1)).until(() -> { + receiptHandleProcessor.scheduleRenewTask(); + try { + ReceiptHandleGroup receiptHandleGroup = receiptHandleProcessor.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(messagingProcessor.findConsumerChannel(Mockito.any(), Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); + receiptHandleProcessor.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, receiptHandle, 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(() -> { + receiptHandleProcessor.scheduleRenewTask(); + try { + ReceiptHandleGroup receiptHandleGroup = receiptHandleProcessor.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(messagingProcessor.findConsumerChannel(Mockito.any(), Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); + receiptHandleProcessor.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, receiptHandle, 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(() -> { + receiptHandleProcessor.scheduleRenewTask(); + try { + ReceiptHandleGroup receiptHandleGroup = receiptHandleProcessor.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); + receiptHandleProcessor.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, newReceiptHandle, messageReceiptHandle); + Mockito.when(messagingProcessor.findConsumerChannel(Mockito.any(), 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())); + receiptHandleProcessor.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 = receiptHandleProcessor.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); + receiptHandleProcessor.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, newReceiptHandle, messageReceiptHandle); + Mockito.when(messagingProcessor.findConsumerChannel(Mockito.any(), 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())); + receiptHandleProcessor.scheduleRenewTask(); + await().atMost(Duration.ofSeconds(1)).until(() -> { + try { + ReceiptHandleGroup receiptHandleGroup = receiptHandleProcessor.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); + receiptHandleProcessor.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, newReceiptHandle, messageReceiptHandle); + SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); + Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(groupConfig); + Mockito.when(messagingProcessor.findConsumerChannel(Mockito.any(), Mockito.eq(GROUP), Mockito.eq(channel))).thenReturn(Mockito.mock(ClientChannelInfo.class)); + receiptHandleProcessor.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); + receiptHandleProcessor.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, receiptHandle, messageReceiptHandle); + receiptHandleProcessor.removeReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, receiptHandle); + SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); + Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(groupConfig); + receiptHandleProcessor.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); + receiptHandleProcessor.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, receiptHandle, messageReceiptHandle); + receiptHandleProcessor.clearGroup(new ReceiptHandleProcessor.ReceiptHandleGroupKey(channel, GROUP)); + SubscriptionGroupConfig groupConfig = new SubscriptionGroupConfig(); + Mockito.when(metadataService.getSubscriptionGroupConfig(Mockito.any(), Mockito.eq(GROUP))).thenReturn(groupConfig); + receiptHandleProcessor.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(messagingProcessor, Mockito.times(1)).registerConsumerListener(listenerArgumentCaptor.capture()); + Channel channel = PROXY_CONTEXT.getVal(ContextVariable.CHANNEL); + receiptHandleProcessor.addReceiptHandle(PROXY_CONTEXT, channel, GROUP, MSG_ID, receiptHandle, messageReceiptHandle); + listenerArgumentCaptor.getValue().handle(ConsumerGroupEvent.CLIENT_UNREGISTER, GROUP, new ClientChannelInfo(channel, "", LanguageCode.JAVA, 0)); + assertTrue(receiptHandleProcessor.receiptHandleGroupMap.isEmpty()); + } + + class MockChannel implements Channel { + @Override + public ChannelId id() { + return new ChannelId() { + @Override + public String asShortText() { + return "short"; + } + + @Override + public String asLongText() { + return "long"; + } + + @Override + public int compareTo(ChannelId o) { + return 1; + } + }; + } + + @Override + public EventLoop eventLoop() { + return null; + } + + @Override + public Channel parent() { + return null; + } + + @Override + public ChannelConfig config() { + return null; + } + + @Override + public boolean isOpen() { + return false; + } + + @Override + public boolean isRegistered() { + return false; + } + + @Override + public boolean isActive() { + return false; + } + + @Override + public ChannelMetadata metadata() { + return null; + } + + @Override + public SocketAddress localAddress() { + return null; + } + + @Override + public SocketAddress remoteAddress() { + return null; + } + + @Override + public ChannelFuture closeFuture() { + return null; + } + + @Override + public boolean isWritable() { + return false; + } + + @Override + public long bytesBeforeUnwritable() { + return 0; + } + + @Override + public long bytesBeforeWritable() { + return 0; + } + + @Override + public Unsafe unsafe() { + return null; + } + + @Override + public ChannelPipeline pipeline() { + return null; + } + + @Override + public ByteBufAllocator alloc() { + return null; + } + + @Override + public ChannelFuture bind(SocketAddress localAddress) { + return null; + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress) { + return null; + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { + return null; + } + + @Override + public ChannelFuture disconnect() { + return null; + } + + @Override + public ChannelFuture close() { + return null; + } + + @Override + public ChannelFuture deregister() { + return null; + } + + @Override + public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture disconnect(ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture close(ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture deregister(ChannelPromise promise) { + return null; + } + + @Override + public Channel read() { + return null; + } + + @Override + public ChannelFuture write(Object msg) { + return null; + } + + @Override + public ChannelFuture write(Object msg, ChannelPromise promise) { + return null; + } + + @Override + public Channel flush() { + return null; + } + + @Override + public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture writeAndFlush(Object msg) { + return null; + } + + @Override + public ChannelPromise newPromise() { + return null; + } + + @Override + public ChannelProgressivePromise newProgressivePromise() { + return null; + } + + @Override + public ChannelFuture newSucceededFuture() { + return null; + } + + @Override + public ChannelFuture newFailedFuture(Throwable cause) { + return null; + } + + @Override + public ChannelPromise voidPromise() { + return null; + } + + @Override + public Attribute attr(AttributeKey key) { + return null; + } + + @Override + public boolean hasAttr(AttributeKey key) { + return false; + } + + @Override + public int compareTo(Channel o) { + return 1; + } + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/TransactionProcessorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/TransactionProcessorTest.java new file mode 100644 index 00000000000..6bffb15bd13 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/TransactionProcessorTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(), commitOrRollbackCaptor.capture(), anyBoolean(), anyString(), anyString())) + .thenReturn(new EndTransactionRequestData("brokerName", new EndTransactionRequestHeader())); + + this.transactionProcessor.endTransaction( + createContext(), + "transactionId", + "msgId", + PRODUCER_GROUP, + transactionStatus, + true, + 3000 + ); + + assertEquals(sysFlag, commitOrRollbackCaptor.getValue().intValue()); + + reset(this.messageService); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannelTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannelTest.java new file mode 100644 index 00000000000..d504fdc5f99 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/channel/RemoteChannelTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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("")); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivityTest.java new file mode 100644 index 00000000000..663a83e3c03 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivityTest.java @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.processor.channel.ChannelProtocolType; +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.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.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.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, RemotingHelper.CLIENT_ID_KEY, CLIENT_ID); + RemotingHelper.setPropertyToAttr(channel, RemotingHelper.LANGUAGE_CODE_KEY, LanguageCode.JAVA); + RemotingHelper.setPropertyToAttr(channel, RemotingHelper.VERSION_KEY, MQVersion.CURRENT_VERSION); + } + + @Test + public void testCreateContext() { + RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null); + ProxyContext context = remotingActivity.createContext(ctx, request); + + Assert.assertEquals(context.getAction(), RemotingHelper.getRequestCodeDesc(RequestCode.PULL_MESSAGE)); + Assert.assertEquals(context.getProtocolType(), ChannelProtocolType.REMOTING.getName()); + Assert.assertEquals(context.getLanguage(), LanguageCode.JAVA.name()); + Assert.assertEquals(context.getClientID(), CLIENT_ID); + Assert.assertEquals(context.getClientVersion(), MQVersion.getVersionDesc(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); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/PullMessageActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/PullMessageActivityTest.java new file mode 100644 index 00000000000..d8ad4518758 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/PullMessageActivityTest.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(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.setBname(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.setBname(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(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.setBname(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)); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/SendMessageActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/SendMessageActivityTest.java new file mode 100644 index 00000000000..9d897642fdb --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/SendMessageActivityTest.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.setBname(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)); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannelManagerTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannelManagerTest.java new file mode 100644 index 00000000000..5a5b441e957 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannelManagerTest.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; + + @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(producerChannel, group, clientId); + assertNotNull(producerRemotingChannel); + assertSame(producerRemotingChannel, this.remotingChannelManager.createProducerChannel(producerChannel, group, clientId)); + + Channel consumerChannel = createMockChannel(); + RemotingChannel consumerRemotingChannel = this.remotingChannelManager.createConsumerChannel(consumerChannel, group, clientId, new HashSet<>()); + assertSame(consumerRemotingChannel, this.remotingChannelManager.createConsumerChannel(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(producerChannel, group, clientId); + assertSame(producerRemotingChannel, this.remotingChannelManager.removeProducerChannel(group, producerRemotingChannel)); + assertTrue(this.remotingChannelManager.groupChannelMap.isEmpty()); + } + { + Channel producerChannel = createMockChannel(); + RemotingChannel producerRemotingChannel = this.remotingChannelManager.createProducerChannel(producerChannel, group, clientId); + assertSame(producerRemotingChannel, this.remotingChannelManager.removeProducerChannel(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(consumerChannel, group, clientId, new HashSet<>()); + assertSame(consumerRemotingChannel, this.remotingChannelManager.removeConsumerChannel(group, consumerRemotingChannel)); + assertTrue(this.remotingChannelManager.groupChannelMap.isEmpty()); + } + { + Channel consumerChannel = createMockChannel(); + RemotingChannel consumerRemotingChannel = this.remotingChannelManager.createConsumerChannel(consumerChannel, group, clientId, new HashSet<>()); + assertSame(consumerRemotingChannel, this.remotingChannelManager.removeConsumerChannel(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(consumerChannel, consumerGroup, clientId, new HashSet<>()); + Channel producerChannel = createMockChannel(); + RemotingChannel producerRemotingChannel = this.remotingChannelManager.createProducerChannel(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()); + } + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannelTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannelTest.java new file mode 100644 index 00000000000..d947fa5d533 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannelTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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))); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/BaseServiceTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/BaseServiceTest.java new file mode 100644 index 00000000000..c97bd5a72b4 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/BaseServiceTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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)); + when(this.topicRouteService.getAllMessageQueueView(any(), eq(CLUSTER_NAME))).thenReturn(new MessageQueueView(CLUSTER_NAME, topicRouteData)); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/admin/DefaultAdminServiceTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/admin/DefaultAdminServiceTest.java new file mode 100644 index 00000000000..cdfc7f7fc23 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/admin/DefaultAdminServiceTest.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/message/ClusterMessageServiceTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/message/ClusterMessageServiceTest.java new file mode 100644 index 00000000000..7e4d25f0c09 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/message/ClusterMessageServiceTest.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/message/LocalMessageServiceTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/message/LocalMessageServiceTest.java new file mode 100644 index 00000000000..84fc6499c06 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/message/LocalMessageServiceTest.java @@ -0,0 +1,451 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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.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.SendMessageRequestHeader; +import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader; +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 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.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, false, queueId, startOffset); + ExtraInfoUtil.buildMsgOffsetInfo(messageOffsetStringBuilder, false, 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.processRequest(Mockito.any(SimpleChannelHandlerContext.class), Mockito.argThat(argument -> { + boolean first = argument.getCode() == RequestCode.CHANGE_MESSAGE_INVISIBLETIME; + boolean second = argument.readCustomHeader() instanceof ChangeInvisibleTimeRequestHeader; + return first && second; + }))).thenReturn(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); + } + + 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()); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataServiceTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataServiceTest.java new file mode 100644 index 00000000000..98bf1104f8b --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataServiceTest.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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.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.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; + + @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); + } + + @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()); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java new file mode 100644 index 00000000000..77a119a296c --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java @@ -0,0 +1,355 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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.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.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.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; + +@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 { + doAnswer((Answer) mock -> { + InvokeCallback invokeCallback = mock.getArgument(3); + ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null); + responseFuture.putResponse(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "")); + invokeCallback.operationComplete(responseFuture); + return null; + }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any()); + + assertNotNull(mqClientAPI.sendHeartbeatAsync(BROKER_ADDR, new HeartbeatData(), TIMEOUT).get()); + } + + @Test + public void testSendMessageAsync() throws Exception { + AtomicReference msgIdRef = new AtomicReference<>(); + doAnswer((Answer) mock -> { + InvokeCallback invokeCallback = mock.getArgument(3); + ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null); + 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(); + responseFuture.putResponse(response); + invokeCallback.operationComplete(responseFuture); + return null; + }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any()); + + 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 { + doAnswer((Answer) mock -> { + InvokeCallback invokeCallback = mock.getArgument(3); + ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null); + 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(); + responseFuture.putResponse(response); + invokeCallback.operationComplete(responseFuture); + return null; + }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any()); + + 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 { + doAnswer((Answer) mock -> { + InvokeCallback invokeCallback = mock.getArgument(3); + ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null); + responseFuture.putResponse(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "")); + invokeCallback.operationComplete(responseFuture); + return null; + }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any()); + + 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 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 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.operationComplete(responseFuture); + 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.operationComplete(responseFuture); + 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.operationComplete(responseFuture); + 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(); + doAnswer((Answer) mock -> { + InvokeCallback invokeCallback = mock.getArgument(3); + ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null); + RemotingCommand response = RemotingCommand.createResponseCommand(SearchOffsetResponseHeader.class); + SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.readCustomHeader(); + responseHeader.setOffset(offset); + response.setCode(ResponseCode.SUCCESS); + response.makeCustomHeaderToNet(); + responseFuture.putResponse(response); + invokeCallback.operationComplete(responseFuture); + return null; + }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any()); + + 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; + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/ProxyClientRemotingProcessorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/ProxyClientRemotingProcessorTest.java new file mode 100644 index 00000000000..a6d807937e2 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/ProxyClientRemotingProcessorTest.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 { + CompletableFuture> proxyRelayResultFuture = new CompletableFuture<>(); + when(proxyRelayService.processCheckTransactionState(any(), any(), any(), any())) + .thenReturn(new RelayData<>( + new TransactionData("brokerName", 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); + 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(1)).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; + } + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/relay/LocalProxyRelayServiceTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/relay/LocalProxyRelayServiceTest.java new file mode 100644 index 00000000000..4ec797d1aaf --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/relay/LocalProxyRelayServiceTest.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/relay/ProxyChannelTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/relay/ProxyChannelTest.java new file mode 100644 index 00000000000..947ae2c24f5 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/relay/ProxyChannelTest.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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); + } + }; + + assertTrue(channel.writeAndFlush(checkTransactionRequest).isSuccess()); + assertTrue(channel.writeAndFlush(consumerRunningInfoRequest).isSuccess()); + assertTrue(channel.writeAndFlush(consumeMessageDirectlyResult).isSuccess()); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteServiceTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteServiceTest.java new file mode 100644 index 00000000000..15d83483b9d --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteServiceTest.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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)); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/LocalTopicRouteServiceTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/LocalTopicRouteServiceTest.java new file mode 100644 index 00000000000..1ad39a1db64 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/LocalTopicRouteServiceTest.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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)); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelectorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelectorTest.java new file mode 100644 index 00000000000..e44ed28f4a6 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelectorTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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)); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java new file mode 100644 index 00000000000..c67f4953d89 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java @@ -0,0 +1,368 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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); + 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)); + } + } + + 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()); + } + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/AbstractTransactionServiceTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/AbstractTransactionServiceTest.java new file mode 100644 index 00000000000..81de5ec843a --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/AbstractTransactionServiceTest.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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, + PRODUCER_GROUP, + RANDOM.nextLong(), + RANDOM.nextLong(), + txId, + message + ); + assertNotNull(transactionData); + + EndTransactionRequestData requestData = transactionService.genEndTransactionRequestHeader( + ctx, + 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, + "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, + PRODUCER_GROUP, + RANDOM.nextLong(), + RANDOM.nextLong(), + txId, + message + ); + transactionService.onSendCheckTransactionStateFailed(ProxyContext.createForInner(this.getClass()), PRODUCER_GROUP, transactionData); + assertNull(transactionService.genEndTransactionRequestHeader( + ctx, + PRODUCER_GROUP, + MessageSysFlag.TRANSACTION_COMMIT_TYPE, + true, + txId, + txId + )); + } +} \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/ClusterTransactionServiceTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/ClusterTransactionServiceTest.java new file mode 100644 index 00000000000..a0063544ecf --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/ClusterTransactionServiceTest.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + 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)); + + 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)); + + 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)); + + 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)); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/TransactionDataManagerTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/TransactionDataManagerTest.java new file mode 100644 index 00000000000..f92a7846c5d --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/TransactionDataManagerTest.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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", + RANDOM.nextLong(), + RANDOM.nextLong(), + txId, + checkTimestamp, + checkImmunityTime + ); + } +} \ No newline at end of file diff --git a/proxy/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/proxy/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 00000000000..ca6ee9cea8e --- /dev/null +++ b/proxy/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/proxy/src/test/resources/rmq-proxy-home/conf/broker.conf b/proxy/src/test/resources/rmq-proxy-home/conf/broker.conf new file mode 100644 index 00000000000..0c0b28b7b8e --- /dev/null +++ b/proxy/src/test/resources/rmq-proxy-home/conf/broker.conf @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 diff --git a/proxy/src/test/resources/rmq-proxy-home/conf/logback_proxy.xml b/proxy/src/test/resources/rmq-proxy-home/conf/logback_proxy.xml new file mode 100644 index 00000000000..091a51fcabf --- /dev/null +++ b/proxy/src/test/resources/rmq-proxy-home/conf/logback_proxy.xml @@ -0,0 +1,420 @@ + + + + + + + ${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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/proxy/src/test/resources/rmq-proxy-home/conf/rmq-proxy.json b/proxy/src/test/resources/rmq-proxy-home/conf/rmq-proxy.json new file mode 100644 index 00000000000..f0873e2a35d --- /dev/null +++ b/proxy/src/test/resources/rmq-proxy-home/conf/rmq-proxy.json @@ -0,0 +1,3 @@ +{ + "proxyMode": "cluster" +} \ No newline at end of file diff --git a/proxy/src/test/resources/rmq.logback-test.xml b/proxy/src/test/resources/rmq.logback-test.xml new file mode 100644 index 00000000000..8695d52d57c --- /dev/null +++ b/proxy/src/test/resources/rmq.logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/remoting/BUILD.bazel b/remoting/BUILD.bazel new file mode 100644 index 00000000000..e3e1bce3b8f --- /dev/null +++ b/remoting/BUILD.bazel @@ -0,0 +1,77 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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_fastjson", + "@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", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":remoting", + "//common", + "//:test_deps", + "@maven//:com_alibaba_fastjson", + "@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", + ], + 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", + ], +) diff --git a/remoting/pom.xml b/remoting/pom.xml index 32320e6d050..f67dc3abc4b 100644 --- a/remoting/pom.xml +++ b/remoting/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.4-SNAPSHOT + 5.1.3 4.0.0 @@ -27,18 +27,24 @@ rocketmq-remoting rocketmq-remoting ${project.version} + + ${basedir}/.. + + - com.alibaba - fastjson + ${project.groupId} + rocketmq-common - io.netty - netty-all + org.apache.commons + commons-lang3 - ${project.groupId} - rocketmq-logging + com.google.code.gson + gson + 2.9.0 + test diff --git a/common/src/main/java/org/apache/rocketmq/common/Configuration.java b/remoting/src/main/java/org/apache/rocketmq/remoting/Configuration.java similarity index 83% rename from common/src/main/java/org/apache/rocketmq/common/Configuration.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/Configuration.java index 064b5e61d11..5b3e5ca3797 100644 --- a/common/src/main/java/org/apache/rocketmq/common/Configuration.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/Configuration.java @@ -15,9 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common; - -import org.apache.rocketmq.logging.InternalLogger; +package org.apache.rocketmq.remoting; import java.io.IOException; import java.lang.reflect.Field; @@ -28,12 +26,15 @@ 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 InternalLogger log; + private final Logger log; - private List configObjectList = new ArrayList(4); + private List configObjectList = new ArrayList<>(4); private String storePath; private boolean storePathFromConfig = false; private Object storePathObject; @@ -45,21 +46,24 @@ public class Configuration { */ private Properties allConfigs = new Properties(); - public Configuration(InternalLogger log) { + public Configuration(Logger log) { this.log = log; } - public Configuration(InternalLogger log, Object... configObjects) { + 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(InternalLogger log, String storePath, Object... configObjects) { + public Configuration(Logger log, String storePath, Object... configObjects) { this(log, configObjects); this.storePath = storePath; } @@ -234,6 +238,24 @@ public String getAllConfigsFormatString() { 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(); } @@ -270,8 +292,28 @@ private String getAllConfigsInternal() { } { - stringBuilder.append(MixAll.properties2String(this.allConfigs)); + 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(); } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/RPCHook.java b/remoting/src/main/java/org/apache/rocketmq/remoting/RPCHook.java index 6292fc09189..ebaeea40af2 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/RPCHook.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/RPCHook.java @@ -23,5 +23,5 @@ public interface RPCHook { void doBeforeRequest(final String remoteAddr, final RemotingCommand request); void doAfterResponse(final String remoteAddr, final RemotingCommand request, - final RemotingCommand response); + final RemotingCommand response); } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingClient.java b/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingClient.java index c0754db634d..ff0b3df95a6 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingClient.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingClient.java @@ -17,8 +17,10 @@ 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.RemotingException; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException; @@ -31,6 +33,8 @@ public interface RemotingClient extends RemotingService { List getNameServerAddressList(); + List getAvailableNameSrvList(); + RemotingCommand invokeSync(final String addr, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException; @@ -43,12 +47,38 @@ void invokeOneway(final String addr, final RemotingCommand request, final long t 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, responseFuture -> { + RemotingCommand response = responseFuture.getResponseCommand(); + if (response != null) { + future.complete(response); + } else { + if (!responseFuture.isSendRequestOK()) { + future.completeExceptionally(new RemotingSendRequestException(addr, responseFuture.getCause())); + } else if (responseFuture.isTimeout()) { + future.completeExceptionally(new RemotingTimeoutException(addr, timeoutMillis, responseFuture.getCause())); + } else { + future.completeExceptionally(new RemotingException(request.toString(), responseFuture.getCause())); + } + } + }); + } catch (Throwable t) { + future.completeExceptionally(t); + } + return future; + } + void registerProcessor(final int requestCode, final NettyRequestProcessor processor, final ExecutorService executor); void setCallbackExecutor(final ExecutorService callbackExecutor); - ExecutorService getCallbackExecutor(); - boolean isChannelWritable(final String addr); + + boolean isAddressReachable(final String addr); + + void closeChannels(final List addrList); } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java index a12c089c74c..8cfa1e1a083 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java @@ -18,7 +18,7 @@ import io.netty.channel.Channel; import java.util.concurrent.ExecutorService; -import org.apache.rocketmq.remoting.common.Pair; +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; @@ -36,6 +36,12 @@ void registerProcessor(final int requestCode, final NettyRequestProcessor proces 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; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingService.java b/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingService.java index 2f887972172..c718f2e65c0 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingService.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingService.java @@ -23,4 +23,9 @@ public interface RemotingService { void shutdown(); void registerRPCHook(RPCHook rpcHook); + + /** + * Remove all rpc hooks. + */ + void clearRPCHook(); } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/common/HeartbeatV2Result.java b/remoting/src/main/java/org/apache/rocketmq/remoting/common/HeartbeatV2Result.java new file mode 100644 index 00000000000..2401233c48e --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/common/HeartbeatV2Result.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingHelper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingHelper.java index 39bbb0da488..75e25a83a15 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingHelper.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingHelper.java @@ -17,43 +17,86 @@ 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 org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import java.util.HashMap; +import java.util.Map; +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.NettySystemConfig; +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; public class RemotingHelper { - public static final String ROCKETMQ_REMOTING = "RocketmqRemoting"; public static final String DEFAULT_CHARSET = "UTF-8"; + public static final String DEFAULT_CIDR_ALL = "0.0.0.0/0"; - private static final InternalLogger log = InternalLoggerFactory.getLogger(ROCKETMQ_REMOTING); + private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); private static final AttributeKey REMOTE_ADDR_KEY = AttributeKey.valueOf("RemoteAddr"); - public static String exceptionSimpleDesc(final Throwable e) { - StringBuilder sb = new StringBuilder(); - if (e != null) { - sb.append(e.toString()); + public static final AttributeKey CLIENT_ID_KEY = AttributeKey.valueOf("ClientId"); + + public static final AttributeKey VERSION_KEY = AttributeKey.valueOf("Version"); - StackTraceElement[] stackTrace = e.getStackTrace(); - if (stackTrace != null && stackTrace.length > 0) { - StackTraceElement element = stackTrace[0]; - sb.append(", "); - sb.append(element.toString()); + public static final AttributeKey LANGUAGE_CODE_KEY = AttributeKey.valueOf("LanguageCode"); + + 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; + } - return sb.toString(); + 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) { @@ -68,8 +111,8 @@ public static RemotingCommand invokeSync(final String addr, final RemotingComman final long timeoutMillis) throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, RemotingCommandException { long beginTime = System.currentTimeMillis(); - SocketAddress socketAddress = RemotingUtil.string2SocketAddress(addr); - SocketChannel socketChannel = RemotingUtil.connect(socketAddress); + SocketAddress socketAddress = NetworkUtil.string2SocketAddress(addr); + SocketChannel socketChannel = connect(socketAddress); if (socketChannel != null) { boolean sendRequestOK = false; @@ -189,8 +232,22 @@ private static String parseChannelRemoteAddr0(final Channel channel) { 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; @@ -198,4 +255,84 @@ public static String parseSocketAddressAddr(SocketAddress socketAddress) { return ""; } + public static int 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 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)); + } } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/common/ServiceThread.java b/remoting/src/main/java/org/apache/rocketmq/remoting/common/ServiceThread.java index 21fd0efd5ee..a4ee814175e 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/common/ServiceThread.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/common/ServiceThread.java @@ -17,14 +17,15 @@ package org.apache.rocketmq.remoting.common; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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 InternalLogger log = InternalLoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING); + private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); private static final long JOIN_TIME = 90 * 1000; protected final Thread thread; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/metrics/RemotingMetricsConstant.java b/remoting/src/main/java/org/apache/rocketmq/remoting/metrics/RemotingMetricsConstant.java new file mode 100644 index 00000000000..730469e5909 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/metrics/RemotingMetricsConstant.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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"; + +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/metrics/RemotingMetricsManager.java b/remoting/src/main/java/org/apache/rocketmq/remoting/metrics/RemotingMetricsManager.java new file mode 100644 index 00000000000..34136f94f7d --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/metrics/RemotingMetricsManager.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 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 { + public static LongHistogram rpcLatency = new NopLongHistogram(); + public static Supplier attributesBuilderSupplier; + + public static AttributesBuilder newAttributesBuilder() { + if (attributesBuilderSupplier == null) { + return Attributes.builder(); + } + return attributesBuilderSupplier.get() + .put(LABEL_PROTOCOL_TYPE, PROTOCOL_TYPE_REMOTING); + } + + public static void initMetrics(Meter meter, Supplier attributesBuilderSupplier) { + RemotingMetricsManager.attributesBuilderSupplier = attributesBuilderSupplier; + rpcLatency = meter.histogramBuilder(HISTOGRAM_RPC_LATENCY) + .setDescription("Rpc latency") + .setUnit("milliseconds") + .ofLongs() + .build(); + } + + public static 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(); + View view = View.builder() + .setAggregation(Aggregation.explicitBucketHistogram(rpcCostTimeBuckets)) + .build(); + return Lists.newArrayList(new Pair<>(selector, view)); + } + + public static 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; + } + +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/FileRegionEncoder.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/FileRegionEncoder.java index 2bd15aea3c2..7373a560703 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/FileRegionEncoder.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/FileRegionEncoder.java @@ -50,9 +50,10 @@ public class FileRegionEncoder extends MessageToByteEncoder { protected void encode(ChannelHandlerContext ctx, FileRegion msg, final ByteBuf out) throws Exception { WritableByteChannel writableByteChannel = new WritableByteChannel() { @Override - public int write(ByteBuffer src) throws IOException { + public int write(ByteBuffer src) { + int prev = out.writerIndex(); out.writeBytes(src); - return out.capacity(); + return out.writerIndex() - prev; } @Override @@ -68,11 +69,11 @@ public void close() throws IOException { long toTransfer = msg.count(); while (true) { - long transferred = msg.transfered(); + long transferred = msg.transferred(); if (toTransfer - transferred <= 0) { break; } msg.transferTo(writableByteChannel, transferred); } } -} \ No newline at end of file +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyClientConfig.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyClientConfig.java index c1b9345c3f4..b2e7df75491 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyClientConfig.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyClientConfig.java @@ -16,6 +16,10 @@ */ 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 @@ -38,11 +42,17 @@ public class NettyClientConfig { private boolean clientPooledByteBufAllocatorEnable = false; private boolean clientCloseSocketIfTimeout = NettySystemConfig.clientCloseSocketIfTimeout; - private boolean useTLS; + 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; + public boolean isClientCloseSocketIfTimeout() { return clientCloseSocketIfTimeout; } @@ -154,4 +164,28 @@ public int getWriteBufferHighWaterMark() { 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 String getSocksProxyConfig() { + return socksProxyConfig; + } + + public void setSocksProxyConfig(String socksProxyConfig) { + this.socksProxyConfig = socksProxyConfig; + } } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyDecoder.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyDecoder.java index f64ab2d109b..19624d74028 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyDecoder.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyDecoder.java @@ -16,18 +16,18 @@ */ 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 java.nio.ByteBuffer; +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.common.RemotingUtil; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class NettyDecoder extends LengthFieldBasedFrameDecoder { - private static final InternalLogger log = InternalLoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING); + 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")); @@ -39,18 +39,18 @@ public NettyDecoder() { @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; } - - ByteBuffer byteBuffer = frame.nioBuffer(); - - return RemotingCommand.decode(byteBuffer); + RemotingCommand cmd = RemotingCommand.decode(frame); + cmd.setProcessTimer(timer); + return cmd; } catch (Exception e) { log.error("decode exception, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), e); - RemotingUtil.closeChannel(ctx.channel()); + RemotingHelper.closeChannel(ctx.channel()); } finally { if (null != frame) { frame.release(); diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyEncoder.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyEncoder.java index 4506e71e97a..2af0af6b725 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyEncoder.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyEncoder.java @@ -20,23 +20,21 @@ import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; -import java.nio.ByteBuffer; +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.common.RemotingUtil; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; import org.apache.rocketmq.remoting.protocol.RemotingCommand; @ChannelHandler.Sharable public class NettyEncoder extends MessageToByteEncoder { - private static final InternalLogger log = InternalLoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING); + private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); @Override public void encode(ChannelHandlerContext ctx, RemotingCommand remotingCommand, ByteBuf out) throws Exception { try { - ByteBuffer header = remotingCommand.encodeHeader(); - out.writeBytes(header); + remotingCommand.fastEncodeHeader(out); byte[] body = remotingCommand.getBody(); if (body != null) { out.writeBytes(body); @@ -46,7 +44,7 @@ public void encode(ChannelHandlerContext ctx, RemotingCommand remotingCommand, B if (remotingCommand != null) { log.error(remotingCommand.toString()); } - RemotingUtil.closeChannel(ctx.channel()); + RemotingHelper.closeChannel(ctx.channel()); } } } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyLogger.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyLogger.java index 1a0c9b53e32..955ffc1bc4f 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyLogger.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyLogger.java @@ -19,8 +19,8 @@ import io.netty.util.internal.logging.InternalLogLevel; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import java.util.concurrent.atomic.AtomicBoolean; @@ -50,12 +50,12 @@ protected io.netty.util.internal.logging.InternalLogger newInstance(String s) { private static class NettyBridgeLogger implements io.netty.util.internal.logging.InternalLogger { - private InternalLogger logger = null; + private Logger logger = null; private static final String EXCEPTION_MESSAGE = "Unexpected exception:"; public NettyBridgeLogger(String name) { - logger = InternalLoggerFactory.getLogger(name); + logger = LoggerFactory.getLogger(name); } @Override diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java index f02518bebe5..44d6a3df4c4 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java @@ -17,11 +17,12 @@ package org.apache.rocketmq.remoting.netty; import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; 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.net.SocketAddress; import java.util.ArrayList; import java.util.HashMap; @@ -36,28 +37,41 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; - -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import org.apache.rocketmq.common.AbortProcessException; +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.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.Pair; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.common.SemaphoreReleaseOnlyOnce; -import org.apache.rocketmq.remoting.common.ServiceThread; 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.protocol.RemotingCommand; import org.apache.rocketmq.remoting.protocol.RemotingSysResponseCode; +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 InternalLogger log = InternalLoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING); + 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. @@ -73,14 +87,14 @@ public abstract class NettyRemotingAbstract { * This map caches all on-going requests. */ protected final ConcurrentMap responseTable = - new ConcurrentHashMap(256); + 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); + new HashMap<>(64); /** * Executor to feed netty events to user defined {@link ChannelEventListener}. @@ -88,9 +102,10 @@ public abstract class NettyRemotingAbstract { 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. + * The default request processor to use in case there is no exact match in {@link #processorTable} per request + * code. */ - protected Pair defaultRequestProcessor; + protected Pair defaultRequestProcessorPair; /** * SSL context via which to create {@link SslHandler}. @@ -100,8 +115,7 @@ public abstract class NettyRemotingAbstract { /** * custom rpc hooks */ - protected List rpcHooks = new ArrayList(); - + protected List rpcHooks = new ArrayList<>(); static { NettyLogger.initNettyLogger(); @@ -148,17 +162,15 @@ public void putNettyEvent(final NettyEvent event) { * * @param ctx Channel handler context. * @param msg incoming remoting command. - * @throws Exception if there were any error while processing the incoming command. */ - public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception { - final RemotingCommand cmd = msg; - if (cmd != null) { - switch (cmd.getType()) { + public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) { + if (msg != null) { + switch (msg.getType()) { case REQUEST_COMMAND: - processRequestCommand(ctx, cmd); + processRequestCommand(ctx, msg); break; case RESPONSE_COMMAND: - processResponseCommand(ctx, cmd); + processResponseCommand(ctx, msg); break; default: break; @@ -168,20 +180,63 @@ public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand ms protected void doBeforeRpcHooks(String addr, RemotingCommand request) { if (rpcHooks.size() > 0) { - for (RPCHook rpcHook: rpcHooks) { + for (RPCHook rpcHook : rpcHooks) { rpcHook.doBeforeRequest(addr, request); } } } - protected void doAfterRpcHooks(String addr, RemotingCommand request, RemotingCommand response) { + public void doAfterRpcHooks(String addr, RemotingCommand request, RemotingCommand response) { if (rpcHooks.size() > 0) { - for (RPCHook rpcHook: rpcHooks) { + for (RPCHook rpcHook : rpcHooks) { rpcHook.doAfterResponse(addr, request, response); } } } + public static void writeResponse(Channel channel, RemotingCommand request, @Nullable RemotingCommand response) { + writeResponse(channel, request, response, null); + } + + public static void writeResponse(Channel channel, RemotingCommand request, @Nullable RemotingCommand response, + Consumer> callback) { + if (response == null) { + return; + } + AttributesBuilder attributesBuilder = RemotingMetricsManager.newAttributesBuilder() + .put(LABEL_IS_LONG_POLLING, request.isSuspended()) + .put(LABEL_REQUEST_CODE, RemotingHelper.getRequestCodeDesc(request.getCode())) + .put(LABEL_RESPONSE_CODE, RemotingHelper.getResponseCodeDesc(response.getCode())); + if (request.isOnewayRPC()) { + attributesBuilder.put(LABEL_RESULT, RESULT_ONEWAY); + RemotingMetricsManager.rpcLatency.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()); + } + attributesBuilder.put(LABEL_RESULT, RemotingMetricsManager.getWriteAndFlushResult(future)); + RemotingMetricsManager.rpcLatency.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()); + attributesBuilder.put(LABEL_RESULT, RESULT_WRITE_CHANNEL_FAILED); + RemotingMetricsManager.rpcLatency.record(request.getProcessTimer().elapsed(TimeUnit.MILLISECONDS), attributesBuilder.build()); + } + } /** * Process incoming request command issued by remote peer. @@ -191,92 +246,104 @@ protected void doAfterRpcHooks(String addr, RemotingCommand request, RemotingCom */ public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) { final Pair matched = this.processorTable.get(cmd.getCode()); - final Pair pair = null == matched ? this.defaultRequestProcessor : matched; + final Pair pair = null == matched ? this.defaultRequestProcessorPair : matched; final int opaque = cmd.getOpaque(); - if (pair != null) { - Runnable run = new Runnable() { - @Override - public void run() { - try { - String remoteAddr = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); - doBeforeRpcHooks(remoteAddr, cmd); - final RemotingResponseCallback callback = new RemotingResponseCallback() { - @Override - public void callback(RemotingCommand response) { - doAfterRpcHooks(remoteAddr, cmd, response); - if (!cmd.isOnewayRPC()) { - if (response != null) { - response.setOpaque(opaque); - response.markResponseType(); - try { - ctx.writeAndFlush(response); - } catch (Throwable e) { - log.error("process request over, but response failed", e); - log.error(cmd.toString()); - log.error(response.toString()); - } - } else { - } - } - } - }; - if (pair.getObject1() instanceof AsyncNettyRequestProcessor) { - AsyncNettyRequestProcessor processor = (AsyncNettyRequestProcessor)pair.getObject1(); - processor.asyncProcessRequest(ctx, cmd, callback); - } else { - NettyRequestProcessor processor = pair.getObject1(); - RemotingCommand response = processor.processRequest(ctx, cmd); - callback.callback(response); - } - } catch (Throwable e) { - log.error("process request exception", e); - log.error(cmd.toString()); - - if (!cmd.isOnewayRPC()) { - final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_ERROR, - RemotingHelper.exceptionSimpleDesc(e)); - response.setOpaque(opaque); - ctx.writeAndFlush(response); - } - } - } - }; + 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); + writeResponse(ctx.channel(), cmd, response); + log.error(RemotingHelper.parseChannelRemoteAddr(ctx.channel()) + error); + 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); - ctx.writeAndFlush(response); - return; + Runnable run = buildProcessRequestHandler(ctx, cmd, pair, opaque); + + if (pair.getObject1().rejectRequest()) { + final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY, + "[REJECTREQUEST]system busy, start flow control for a while"); + response.setOpaque(opaque); + writeResponse(ctx.channel(), cmd, response); + 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); + writeResponse(ctx.channel(), cmd, response); + } catch (Throwable e) { + AttributesBuilder attributesBuilder = RemotingMetricsManager.newAttributesBuilder() + .put(LABEL_REQUEST_CODE, RemotingHelper.getRequestCodeDesc(cmd.getCode())) + .put(LABEL_RESULT, RESULT_PROCESS_REQUEST_FAILED); + RemotingMetricsManager.rpcLatency.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; + try { - final RequestTask requestTask = new RequestTask(run, ctx.channel(), cmd); - 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()); + String remoteAddr = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); + try { + doBeforeRpcHooks(remoteAddr, cmd); + } catch (AbortProcessException e) { + throw e; + } catch (Exception e) { + exception = e; } + 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; + } + + writeResponse(ctx.channel(), cmd, response); + } catch (AbortProcessException e) { + response = RemotingCommand.createResponseCommand(e.getResponseCode(), e.getErrorMessage()); + response.setOpaque(opaque); + writeResponse(ctx.channel(), cmd, response); + } catch (Throwable e) { + log.error("process request exception", e); + log.error(cmd.toString()); + if (!cmd.isOnewayRPC()) { - final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY, - "[OVERLOAD]system busy, start flow control for a while"); + response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_ERROR, + UtilAll.exceptionSimpleDesc(e)); response.setOpaque(opaque); - ctx.writeAndFlush(response); + writeResponse(ctx.channel(), cmd, response); } } - } else { - String error = " request type " + cmd.getCode() + " not supported"; - final RemotingCommand response = - RemotingCommand.createResponseCommand(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED, error); - response.setOpaque(opaque); - ctx.writeAndFlush(response); - log.error(RemotingHelper.parseChannelRemoteAddr(ctx.channel()) + error); - } + }; } /** @@ -311,18 +378,15 @@ public void processResponseCommand(ChannelHandlerContext ctx, RemotingCommand cm private void executeInvokeCallback(final ResponseFuture responseFuture) { boolean runInThisThread = false; ExecutorService executor = this.getCallbackExecutor(); - if (executor != null) { + if (executor != null && !executor.isShutdown()) { try { - executor.submit(new Runnable() { - @Override - public void run() { - try { - responseFuture.executeInvokeCallback(); - } catch (Throwable e) { - log.warn("execute callback in executor exception, and callback throw", e); - } finally { - responseFuture.release(); - } + 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) { @@ -344,29 +408,24 @@ public void run() { } } - - - /** - * Custom RPC hook. - * Just be compatible with the previous version, use getRPCHooks instead. - */ - @Deprecated - protected RPCHook getRPCHook() { - if (rpcHooks.size() > 0) { - return rpcHooks.get(0); - } - return null; - } - /** * Custom RPC hooks. * * @return RPC hooks if specified; null otherwise. */ - public List getRPCHooks() { + public List getRPCHook() { return rpcHooks; } + public void registerRPCHook(RPCHook rpcHook) { + if (rpcHook != null && !rpcHooks.contains(rpcHook)) { + rpcHooks.add(rpcHook); + } + } + + public void clearRPCHook() { + rpcHooks.clear(); + } /** * This method specifies thread pool to use while invoking callback methods. @@ -382,7 +441,7 @@ public List getRPCHooks() { *

    */ public void scanResponseTable() { - final List rfList = new LinkedList(); + final List rfList = new LinkedList<>(); Iterator> it = this.responseTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); @@ -408,27 +467,24 @@ public void scanResponseTable() { public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException { + //get the request id final int opaque = request.getOpaque(); try { final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null); this.responseTable.put(opaque, responseFuture); final SocketAddress addr = channel.remoteAddress(); - channel.writeAndFlush(request).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) throws Exception { - if (f.isSuccess()) { - responseFuture.setSendRequestOK(true); - return; - } else { - responseFuture.setSendRequestOK(false); - } - - responseTable.remove(opaque); - responseFuture.setCause(f.cause()); - responseFuture.putResponse(null); - log.warn("send a request command to channel <" + addr + "> failed."); + channel.writeAndFlush(request).addListener((ChannelFutureListener) f -> { + if (f.isSuccess()) { + responseFuture.setSendRequestOK(true); + return; } + + responseFuture.setSendRequestOK(false); + responseTable.remove(opaque); + responseFuture.setCause(f.cause()); + responseFuture.putResponse(null); + log.warn("Failed to write a request command to {}, caused by underlying I/O operation failure", addr); }); RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis); @@ -464,16 +520,13 @@ public void invokeAsyncImpl(final Channel channel, final RemotingCommand request final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis - costTime, invokeCallback, once); this.responseTable.put(opaque, responseFuture); try { - channel.writeAndFlush(request).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) throws Exception { - if (f.isSuccess()) { - responseFuture.setSendRequestOK(true); - return; - } - requestFail(opaque); - log.warn("send a request command to channel <{}> failed.", RemotingHelper.parseChannelRemoteAddr(channel)); + channel.writeAndFlush(request).addListener((ChannelFutureListener) f -> { + if (f.isSuccess()) { + responseFuture.setSendRequestOK(true); + return; } + requestFail(opaque); + log.warn("send a request command to channel <{}> failed.", RemotingHelper.parseChannelRemoteAddr(channel)); }); } catch (Exception e) { responseFuture.release(); @@ -513,13 +566,12 @@ private void requestFail(final int opaque) { /** * 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) { - Iterator> it = responseTable.entrySet().iterator(); - while (it.hasNext()) { - Entry entry = it.next(); - if (entry.getValue().getProcessChannel() == channel) { + for (Entry entry : responseTable.entrySet()) { + if (entry.getValue().getChannel() == channel) { Integer opaque = entry.getKey(); if (opaque != null) { requestFail(opaque); @@ -535,13 +587,10 @@ public void invokeOnewayImpl(final Channel channel, final RemotingCommand reques if (acquired) { final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreOneway); try { - channel.writeAndFlush(request).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture f) throws Exception { - once.release(); - if (!f.isSuccess()) { - log.warn("send a request command to channel <" + channel.remoteAddress() + "> failed."); - } + 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) { @@ -566,11 +615,11 @@ public void operationComplete(ChannelFuture f) throws Exception { } class NettyEventExecutor extends ServiceThread { - private final LinkedBlockingQueue eventQueue = new LinkedBlockingQueue(); - private final int maxSize = 10000; + 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 { diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java index 5ced3b7eb4d..afd779c830f 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java @@ -16,7 +16,10 @@ */ package org.apache.rocketmq.remoting.netty; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; 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; @@ -31,46 +34,58 @@ 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.HashedWheelTimer; +import io.netty.util.Timeout; +import io.netty.util.TimerTask; import io.netty.util.concurrent.DefaultEventExecutorGroup; +import io.netty.util.concurrent.EventExecutorGroup; 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.Timer; -import java.util.TimerTask; +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.Executors; -import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; 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 org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.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.RemotingClient; -import org.apache.rocketmq.remoting.common.Pair; import org.apache.rocketmq.remoting.common.RemotingHelper; -import org.apache.rocketmq.remoting.common.RemotingUtil; 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.proxy.SocksProxyConfig; public class NettyRemotingClient extends NettyRemotingAbstract implements RemotingClient { - private static final InternalLogger log = InternalLoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING); + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); private static final long LOCK_TIMEOUT_MILLIS = 3000; @@ -78,23 +93,27 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti private final Bootstrap bootstrap = new Bootstrap(); private final EventLoopGroup eventLoopGroupWorker; private final Lock lockChannelTables = new ReentrantLock(); - private final ConcurrentMap channelTables = new ConcurrentHashMap(); + private final Map proxyMap = new HashMap<>(); + private final ConcurrentHashMap bootstrapMap = new ConcurrentHashMap<>(); + private final ConcurrentMap channelTables = new ConcurrentHashMap<>(); - private final Timer timer = new Timer("ClientHouseKeepingService", true); + private final HashedWheelTimer timer = new HashedWheelTimer(r -> new Thread(r, "ClientHouseKeepingService")); - private final AtomicReference> namesrvAddrList = new AtomicReference>(); - private final AtomicReference namesrvAddrChoosed = new AtomicReference(); + 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 DefaultEventExecutorGroup defaultEventExecutorGroup; + private EventExecutorGroup defaultEventExecutorGroup; public NettyRemotingClient(final NettyClientConfig nettyClientConfig) { this(nettyClientConfig, null); @@ -102,41 +121,44 @@ public NettyRemotingClient(final NettyClientConfig nettyClientConfig) { 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 ThreadFactory() { - private AtomicInteger threadIndex = new AtomicInteger(0); - - @Override - public Thread newThread(Runnable r) { - return new Thread(r, "NettyClientPublicExecutor_" + this.threadIndex.incrementAndGet()); - } - }); + this.publicExecutor = Executors.newFixedThreadPool(publicThreadNums, new ThreadFactoryImpl("NettyClientPublicExecutor_")); - this.eventLoopGroupWorker = new NioEventLoopGroup(1, new ThreadFactory() { - private AtomicInteger threadIndex = new AtomicInteger(0); + this.scanExecutor = new ThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS, + new ArrayBlockingQueue<>(32), new ThreadFactoryImpl("NettyClientScan_thread_")); - @Override - public Thread newThread(Runnable r) { - return new Thread(r, String.format("NettyClientSelector_%d", this.threadIndex.incrementAndGet())); - } - }); + 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); - log.info("SSL enabled for client"); + LOGGER.info("SSL enabled for client"); } catch (IOException e) { - log.error("Failed to create SSLContext", e); + LOGGER.error("Failed to create SSLContext", e); } catch (CertificateException e) { - log.error("Failed to create SSLContext", e); + LOGGER.error("Failed to create SSLContext", e); throw new RuntimeException("Failed to create SSLContext", e); } } @@ -144,24 +166,25 @@ public Thread newThread(Runnable r) { private static int initValueIndex() { Random r = new Random(); + return r.nextInt(999); + } - return Math.abs(r.nextInt() % 999) % 999; + private void loadSocksProxyJson() { + Map sockProxyMap = JSON.parseObject( + nettyClientConfig.getSocksProxyConfig(), new TypeReference>() { + }); + if (sockProxyMap != null) { + proxyMap.putAll(sockProxyMap); + } } @Override public void start() { - this.defaultEventExecutorGroup = new DefaultEventExecutorGroup( - nettyClientConfig.getClientWorkerThreads(), - new ThreadFactory() { - - private AtomicInteger threadIndex = new AtomicInteger(0); - - @Override - public Thread newThread(Runnable r) { - return new Thread(r, "NettyClientWorkerThread_" + this.threadIndex.incrementAndGet()); - } - }); - + 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) @@ -173,13 +196,13 @@ public void initChannel(SocketChannel ch) throws Exception { if (nettyClientConfig.isUseTLS()) { if (null != sslContext) { pipeline.addFirst(defaultEventExecutorGroup, "sslHandler", sslContext.newHandler(ch.alloc())); - log.info("Prepend SSL handler"); + LOGGER.info("Prepend SSL handler"); } else { - log.warn("Connections are insecure as SSLContext is null!"); + LOGGER.warn("Connections are insecure as SSLContext is null!"); } } - pipeline.addLast( - defaultEventExecutorGroup, + ch.pipeline().addLast( + nettyClientConfig.isDisableNettyWorkerGroup() ? null : defaultEventExecutorGroup, new NettyEncoder(), new NettyDecoder(), new IdleStateHandler(0, 0, nettyClientConfig.getClientChannelMaxIdleTimeSeconds()), @@ -188,43 +211,149 @@ public void initChannel(SocketChannel ch) throws Exception { } }); if (nettyClientConfig.getClientSocketSndBufSize() > 0) { - log.info("client set SO_SNDBUF to {}", nettyClientConfig.getClientSocketSndBufSize()); + LOGGER.info("client set SO_SNDBUF to {}", nettyClientConfig.getClientSocketSndBufSize()); handler.option(ChannelOption.SO_SNDBUF, nettyClientConfig.getClientSocketSndBufSize()); } if (nettyClientConfig.getClientSocketRcvBufSize() > 0) { - log.info("client set SO_RCVBUF to {}", nettyClientConfig.getClientSocketRcvBufSize()); + LOGGER.info("client set SO_RCVBUF to {}", nettyClientConfig.getClientSocketRcvBufSize()); handler.option(ChannelOption.SO_RCVBUF, nettyClientConfig.getClientSocketRcvBufSize()); } if (nettyClientConfig.getWriteBufferLowWaterMark() > 0 && nettyClientConfig.getWriteBufferHighWaterMark() > 0) { - log.info("client set netty WRITE_BUFFER_WATER_MARK to {},{}", - nettyClientConfig.getWriteBufferLowWaterMark(), nettyClientConfig.getWriteBufferHighWaterMark()); + 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())); + nettyClientConfig.getWriteBufferLowWaterMark(), nettyClientConfig.getWriteBufferHighWaterMark())); + } + if (nettyClientConfig.isClientPooledByteBufAllocatorEnable()) { + handler.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); } - this.timer.scheduleAtFixedRate(new TimerTask() { + TimerTask timerTaskScanResponseTable = new TimerTask() { @Override - public void run() { + public void run(Timeout timeout) { try { NettyRemotingClient.this.scanResponseTable(); } catch (Throwable e) { - log.error("scanResponseTable exception", e); + LOGGER.error("scanResponseTable exception", e); + } finally { + timer.newTimeout(this, 1000, TimeUnit.MILLISECONDS); + } + } + }; + this.timer.newTimeout(timerTaskScanResponseTable, 1000 * 3, TimeUnit.MILLISECONDS); + + 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); } } - }, 1000 * 3, 1000); + }; + this.timer.newTimeout(timerTaskScanAvailableNameSrv, 0, TimeUnit.MILLISECONDS); + } - if (this.channelEventListener != null) { - this.nettyEventExecutor.start(); + 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; + } + + 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 RemotingUtil, it will directly resolve the domain + private String[] getHostAndPort(String address) { + return address.split(":"); } @Override public void shutdown() { try { - this.timer.cancel(); + this.timer.stop(); - for (ChannelWrapper cw : this.channelTables.values()) { - this.closeChannel(null, cw.getChannel()); + for (String addr : this.channelTables.keySet()) { + this.closeChannel(addr, this.channelTables.get(addr).getChannel()); } this.channelTables.clear(); @@ -239,21 +368,30 @@ public void shutdown() { this.defaultEventExecutorGroup.shutdownGracefully(); } } catch (Exception e) { - log.error("NettyRemotingClient shutdown exception, ", e); + LOGGER.error("NettyRemotingClient shutdown exception, ", e); } if (this.publicExecutor != null) { try { this.publicExecutor.shutdown(); } catch (Exception e) { - log.error("NettyRemotingServer shutdown 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) + if (null == channel) { return; + } final String addrRemote = null == addr ? RemotingHelper.parseChannelRemoteAddr(channel) : addr; @@ -263,46 +401,40 @@ public void closeChannel(final String addr, final Channel channel) { boolean removeItemFromTable = true; final ChannelWrapper prevCW = this.channelTables.get(addrRemote); - log.info("closeChannel: begin close the channel[{}] Found: {}", addrRemote, prevCW != null); + LOGGER.info("closeChannel: begin close the channel[{}] Found: {}", addrRemote, prevCW != null); if (null == prevCW) { - log.info("closeChannel: the channel[{}] has been removed from the channel table before", addrRemote); + LOGGER.info("closeChannel: the channel[{}] has been removed from the channel table before", addrRemote); removeItemFromTable = false; } else if (prevCW.getChannel() != channel) { - log.info("closeChannel: the channel[{}] has been closed before, and has been created again, nothing to do.", + LOGGER.info("closeChannel: the channel[{}] has been closed before, and has been created again, nothing to do.", addrRemote); removeItemFromTable = false; } if (removeItemFromTable) { this.channelTables.remove(addrRemote); - log.info("closeChannel: the channel[{}] was removed from channel table", addrRemote); + LOGGER.info("closeChannel: the channel[{}] was removed from channel table", addrRemote); } - RemotingUtil.closeChannel(channel); + RemotingHelper.closeChannel(channel); } catch (Exception e) { - log.error("closeChannel: close the channel exception", e); + LOGGER.error("closeChannel: close the channel exception", e); } finally { this.lockChannelTables.unlock(); } } else { - log.warn("closeChannel: try to lock channel table, but timeout, {}ms", LOCK_TIMEOUT_MILLIS); + LOGGER.warn("closeChannel: try to lock channel table, but timeout, {}ms", LOCK_TIMEOUT_MILLIS); } } catch (InterruptedException e) { - log.error("closeChannel exception", e); - } - } - - @Override - public void registerRPCHook(RPCHook rpcHook) { - if (rpcHook != null && !rpcHooks.contains(rpcHook)) { - rpcHooks.add(rpcHook); + LOGGER.error("closeChannel exception", e); } } public void closeChannel(final Channel channel) { - if (null == channel) + if (null == channel) { return; + } try { if (this.lockChannelTables.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { @@ -323,25 +455,25 @@ public void closeChannel(final Channel channel) { } if (null == prevCW) { - log.info("eventCloseChannel: the channel[{}] has been removed from the channel table before", addrRemote); + LOGGER.info("eventCloseChannel: the channel[{}] has been removed from the channel table before", addrRemote); removeItemFromTable = false; } if (removeItemFromTable) { this.channelTables.remove(addrRemote); - log.info("closeChannel: the channel[{}] was removed from channel table", addrRemote); - RemotingUtil.closeChannel(channel); + LOGGER.info("closeChannel: the channel[{}] was removed from channel table", addrRemote); + RemotingHelper.closeChannel(channel); } } catch (Exception e) { - log.error("closeChannel: close the channel exception", e); + LOGGER.error("closeChannel: close the channel exception", e); } finally { this.lockChannelTables.unlock(); } } else { - log.warn("closeChannel: try to lock channel table, but timeout, {}ms", LOCK_TIMEOUT_MILLIS); + LOGGER.warn("closeChannel: try to lock channel table, but timeout, {}ms", LOCK_TIMEOUT_MILLIS); } } catch (InterruptedException e) { - log.error("closeChannel exception", e); + LOGGER.error("closeChannel exception", e); } } @@ -356,20 +488,30 @@ public void updateNameServerAddressList(List addrs) { } else if (addrs.size() != old.size()) { update = true; } else { - for (int i = 0; i < addrs.size() && !update; i++) { - if (!old.contains(addrs.get(i))) { + for (String addr : addrs) { + if (!old.contains(addr)) { update = true; + break; } } } if (update) { Collections.shuffle(addrs); - log.info("name server address updated. NEW : {} , OLD: {}", addrs, old); + LOGGER.info("name server address updated. NEW : {} , OLD: {}", addrs, old); this.namesrvAddrList.set(addrs); - if (!addrs.contains(this.namesrvAddrChoosed.get())) { - this.namesrvAddrChoosed.set(null); + // should close the channel if choosed addr is not exist. + if (this.namesrvAddrChoosed.get() != null && !addrs.contains(this.namesrvAddrChoosed.get())) { + String namesrvAddr = this.namesrvAddrChoosed.get(); + for (String addr : this.channelTables.keySet()) { + if (addr.contains(namesrvAddr)) { + ChannelWrapper channelWrapper = this.channelTables.get(addr); + if (channelWrapper != null) { + closeChannel(channelWrapper.getChannel()); + } + } + } } } } @@ -380,26 +522,28 @@ public RemotingCommand invokeSync(String addr, final RemotingCommand request, lo 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()) { try { - doBeforeRpcHooks(addr, request); + doBeforeRpcHooks(channelRemoteAddr, request); long costTime = System.currentTimeMillis() - beginStartTime; if (timeoutMillis < costTime) { - throw new RemotingTimeoutException("invokeSync call the addr[" + addr + "] timeout"); + throw new RemotingTimeoutException("invokeSync call the addr[" + channelRemoteAddr + "] timeout"); } RemotingCommand response = this.invokeSyncImpl(channel, request, timeoutMillis - costTime); - doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(channel), request, response); + doAfterRpcHooks(channelRemoteAddr, request, response); + this.updateChannelLastResponseTime(addr); return response; } catch (RemotingSendRequestException e) { - log.warn("invokeSync: send request exception, so close the channel[{}]", addr); + LOGGER.warn("invokeSync: send request exception, so close the channel[{}]", channelRemoteAddr); this.closeChannel(addr, channel); throw e; } catch (RemotingTimeoutException e) { if (nettyClientConfig.isClientCloseSocketIfTimeout()) { this.closeChannel(addr, channel); - log.warn("invokeSync: close socket because of timeout, {}ms, {}", timeoutMillis, addr); + LOGGER.warn("invokeSync: close socket because of timeout, {}ms, {}", timeoutMillis, channelRemoteAddr); } - log.warn("invokeSync: wait response timeout exception, the channel[{}]", addr); + LOGGER.warn("invokeSync: wait response timeout exception, the channel[{}]", channelRemoteAddr); throw e; } } else { @@ -408,7 +552,49 @@ public RemotingCommand invokeSync(String addr, final RemotingCommand request, lo } } - private Channel getAndCreateChannel(final String addr) throws RemotingConnectException, InterruptedException { + @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() == 11 || cmd.getCode() == 361)) { + 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 Channel getAndCreateChannel(final String addr) throws InterruptedException { if (null == addr) { return getAndCreateNameserverChannel(); } @@ -421,7 +607,7 @@ private Channel getAndCreateChannel(final String addr) throws RemotingConnectExc return this.createChannel(addr); } - private Channel getAndCreateNameserverChannel() throws RemotingConnectException, InterruptedException { + private Channel getAndCreateNameserverChannel() throws InterruptedException { String addr = this.namesrvAddrChoosed.get(); if (addr != null) { ChannelWrapper cw = this.channelTables.get(addr); @@ -449,7 +635,7 @@ private Channel getAndCreateNameserverChannel() throws RemotingConnectException, String newAddr = addrList.get(index); this.namesrvAddrChoosed.set(newAddr); - log.info("new name server is chosen. OLD: {} , NEW: {}. namesrvIndex = {}", addr, newAddr, namesrvIndex); + LOGGER.info("new name server is chosen. OLD: {} , NEW: {}. namesrvIndex = {}", addr, newAddr, namesrvIndex); Channel channelNew = this.createChannel(newAddr); if (channelNew != null) { return channelNew; @@ -457,11 +643,13 @@ private Channel getAndCreateNameserverChannel() throws RemotingConnectException, } throw new RemotingConnectException(addrList.toString()); } + } catch (Exception e) { + LOGGER.error("getAndCreateNameserverChannel: create name server channel exception", e); } finally { this.namesrvChannelLock.unlock(); } } else { - log.warn("getAndCreateNameserverChannel: try to lock name server, but timeout, {}ms", LOCK_TIMEOUT_MILLIS); + LOGGER.warn("getAndCreateNameserverChannel: try to lock name server, but timeout, {}ms", LOCK_TIMEOUT_MILLIS); } return null; @@ -492,31 +680,33 @@ private Channel createChannel(final String addr) throws InterruptedException { } if (createNewConnection) { - ChannelFuture channelFuture = this.bootstrap.connect(RemotingHelper.string2SocketAddress(addr)); - log.info("createChannel: begin to connect remote host[{}] asynchronously", addr); + String[] hostAndPort = getHostAndPort(addr); + ChannelFuture channelFuture = fetchBootstrap(addr) + .connect(hostAndPort[0], Integer.parseInt(hostAndPort[1])); + LOGGER.info("createChannel: begin to connect remote host[{}] asynchronously", addr); cw = new ChannelWrapper(channelFuture); this.channelTables.put(addr, cw); } } catch (Exception e) { - log.error("createChannel: create channel exception", e); + LOGGER.error("createChannel: create channel exception", e); } finally { this.lockChannelTables.unlock(); } } else { - log.warn("createChannel: try to lock channel table, but timeout, {}ms", LOCK_TIMEOUT_MILLIS); + LOGGER.warn("createChannel: try to lock channel table, but timeout, {}ms", LOCK_TIMEOUT_MILLIS); } if (cw != null) { ChannelFuture channelFuture = cw.getChannelFuture(); if (channelFuture.awaitUninterruptibly(this.nettyClientConfig.getConnectTimeoutMillis())) { if (cw.isOK()) { - log.info("createChannel: connect remote host[{}] success, {}", addr, channelFuture.toString()); + LOGGER.info("createChannel: connect remote host[{}] success, {}", addr, channelFuture.toString()); return cw.getChannel(); } else { - log.warn("createChannel: connect remote host[" + addr + "] failed, " + channelFuture.toString(), channelFuture.cause()); + LOGGER.warn("createChannel: connect remote host[" + addr + "] failed, " + channelFuture.toString()); } } else { - log.warn("createChannel: connect remote host[{}] timeout {}ms, {}", addr, this.nettyClientConfig.getConnectTimeoutMillis(), + LOGGER.warn("createChannel: connect remote host[{}] timeout {}ms, {}", addr, this.nettyClientConfig.getConnectTimeoutMillis(), channelFuture.toString()); } } @@ -530,16 +720,17 @@ public void invokeAsync(String addr, RemotingCommand request, long timeoutMillis RemotingSendRequestException { long beginStartTime = System.currentTimeMillis(); final Channel channel = this.getAndCreateChannel(addr); + String channelRemoteAddr = RemotingHelper.parseChannelRemoteAddr(channel); if (channel != null && channel.isActive()) { try { - doBeforeRpcHooks(addr, request); + doBeforeRpcHooks(channelRemoteAddr, request); long costTime = System.currentTimeMillis() - beginStartTime; if (timeoutMillis < costTime) { - throw new RemotingTooMuchRequestException("invokeAsync call the addr[" + addr + "] timeout"); + throw new RemotingTooMuchRequestException("invokeAsync call the addr[" + channelRemoteAddr + "] timeout"); } - this.invokeAsyncImpl(channel, request, timeoutMillis - costTime, invokeCallback); + this.invokeAsyncImpl(channel, request, timeoutMillis - costTime, new InvokeCallbackWrapper(invokeCallback, addr)); } catch (RemotingSendRequestException e) { - log.warn("invokeAsync: send request exception, so close the channel[{}]", addr); + LOGGER.warn("invokeAsync: send request exception, so close the channel[{}]", channelRemoteAddr); this.closeChannel(addr, channel); throw e; } @@ -553,18 +744,19 @@ public void invokeAsync(String addr, RemotingCommand request, long timeoutMillis public void invokeOneway(String addr, RemotingCommand request, long timeoutMillis) throws InterruptedException, RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException { final Channel channel = this.getAndCreateChannel(addr); + String channelRemoteAddr = RemotingHelper.parseChannelRemoteAddr(channel); if (channel != null && channel.isActive()) { try { - doBeforeRpcHooks(addr, request); + doBeforeRpcHooks(channelRemoteAddr, request); this.invokeOnewayImpl(channel, request, timeoutMillis); } catch (RemotingSendRequestException e) { - log.warn("invokeOneway: send request exception, so close the channel[{}]", addr); + LOGGER.warn("invokeOneway: send request exception, so close the channel[{}]", channelRemoteAddr); this.closeChannel(addr, channel); throw e; } } else { this.closeChannel(addr, channel); - throw new RemotingConnectException(addr); + throw new RemotingConnectException(channelRemoteAddr); } } @@ -575,7 +767,7 @@ public void registerProcessor(int requestCode, NettyRequestProcessor processor, executorThis = this.publicExecutor; } - Pair pair = new Pair(processor, executorThis); + Pair pair = new Pair<>(processor, executorThis); this.processorTable.put(requestCode, pair); } @@ -588,11 +780,30 @@ public boolean isChannelWritable(String addr) { 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; @@ -600,6 +811,9 @@ public ChannelEventListener getChannelEventListener() { @Override public ExecutorService getCallbackExecutor() { + if (nettyClientConfig.isDisableCallbackExecutor()) { + return null; + } return callbackExecutor != null ? callbackExecutor : publicExecutor; } @@ -608,11 +822,71 @@ 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 (String addr : this.channelTables.keySet()) { + ChannelWrapper channelWrapper = this.channelTables.get(addr); + 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); + } + } + }); + } + } + static class ChannelWrapper { private final ChannelFuture channelFuture; + // only affected by sync or async request, oneway is not included. + private long lastResponseTime; public ChannelWrapper(ChannelFuture channelFuture) { this.channelFuture = channelFuture; + this.lastResponseTime = System.currentTimeMillis(); } public boolean isOK() { @@ -630,6 +904,33 @@ private Channel getChannel() { public ChannelFuture getChannelFuture() { return channelFuture; } + + public long getLastResponseTime() { + return this.lastResponseTime; + } + + public void updateLastResponseTime() { + this.lastResponseTime = System.currentTimeMillis(); + } + } + + 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) { + if (responseFuture != null && responseFuture.isSendRequestOK() && responseFuture.getResponseCommand() != null) { + NettyRemotingClient.this.updateChannelLastResponseTime(addr); + } + this.invokeCallback.operationComplete(responseFuture); + } } class NettyClientHandler extends SimpleChannelInboundHandler { @@ -646,7 +947,7 @@ public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, Sock ChannelPromise promise) throws Exception { final String local = localAddress == null ? "UNKNOWN" : RemotingHelper.parseSocketAddressAddr(localAddress); final String remote = remoteAddress == null ? "UNKNOWN" : RemotingHelper.parseSocketAddressAddr(remoteAddress); - log.info("NETTY CLIENT PIPELINE: CONNECT {} => {}", local, remote); + LOGGER.info("NETTY CLIENT PIPELINE: CONNECT {} => {}", local, remote); super.connect(ctx, remoteAddress, localAddress, promise); @@ -658,7 +959,7 @@ public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, Sock @Override public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); - log.info("NETTY CLIENT PIPELINE: DISCONNECT {}", remoteAddress); + LOGGER.info("NETTY CLIENT PIPELINE: DISCONNECT {}", remoteAddress); closeChannel(ctx.channel()); super.disconnect(ctx, promise); @@ -670,7 +971,7 @@ public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws @Override public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); - log.info("NETTY CLIENT PIPELINE: CLOSE {}", remoteAddress); + LOGGER.info("NETTY CLIENT PIPELINE: CLOSE {}", remoteAddress); closeChannel(ctx.channel()); super.close(ctx, promise); NettyRemotingClient.this.failFast(ctx.channel()); @@ -679,13 +980,21 @@ public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exce } } + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); + LOGGER.info("NETTY CLIENT PIPELINE: channelInactive, the channel[{}]", remoteAddress); + 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()); - log.warn("NETTY CLIENT PIPELINE: IDLE exception [{}]", remoteAddress); + LOGGER.warn("NETTY CLIENT PIPELINE: IDLE exception [{}]", remoteAddress); closeChannel(ctx.channel()); if (NettyRemotingClient.this.channelEventListener != null) { NettyRemotingClient.this @@ -700,8 +1009,8 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); - log.warn("NETTY CLIENT PIPELINE: exceptionCaught {}", remoteAddress); - log.warn("NETTY CLIENT PIPELINE: exceptionCaught exception.", cause); + LOGGER.warn("NETTY CLIENT PIPELINE: exceptionCaught {}", remoteAddress); + LOGGER.warn("NETTY CLIENT PIPELINE: exceptionCaught exception.", cause); closeChannel(ctx.channel()); if (NettyRemotingClient.this.channelEventListener != null) { NettyRemotingClient.this.putNettyEvent(new NettyEvent(NettyEventType.EXCEPTION, remoteAddress, ctx.channel())); diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java index 22440af943f..9f39d672e7b 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java @@ -26,6 +26,7 @@ import io.netty.channel.ChannelHandlerContext; 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; @@ -38,57 +39,72 @@ import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import io.netty.handler.timeout.IdleStateHandler; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timeout; +import io.netty.util.TimerTask; import io.netty.util.concurrent.DefaultEventExecutorGroup; import java.io.IOException; import java.net.InetSocketAddress; import java.security.cert.CertificateException; import java.util.NoSuchElementException; -import java.util.Timer; -import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +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.NetworkUtil; +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.RemotingServer; -import org.apache.rocketmq.remoting.common.Pair; import org.apache.rocketmq.remoting.common.RemotingHelper; -import org.apache.rocketmq.remoting.common.RemotingUtil; 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; +@SuppressWarnings("NullableProblems") public class NettyRemotingServer extends NettyRemotingAbstract implements RemotingServer { - private static final InternalLogger log = InternalLoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING); + 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; private final EventLoopGroup eventLoopGroupSelector; private final EventLoopGroup eventLoopGroupBoss; private final NettyServerConfig nettyServerConfig; private final ExecutorService publicExecutor; + private final ScheduledExecutorService scheduledExecutorService; private final ChannelEventListener channelEventListener; - private final Timer timer = new Timer("ServerHouseKeepingService", true); - private DefaultEventExecutorGroup defaultEventExecutorGroup; + private final HashedWheelTimer timer = new HashedWheelTimer(r -> new Thread(r, "ServerHouseKeepingService")); + private DefaultEventExecutorGroup defaultEventExecutorGroup; - private int port = 0; + /** + * NettyRemotingServer may hold multiple SubRemotingServer, each server will be stored in this container with a + * ListenPort key. + */ + private final ConcurrentMap remotingServerTable = new ConcurrentHashMap<>(); - private static final String HANDSHAKE_HANDLER_NAME = "handshakeHandler"; - private static final String TLS_HANDLER_NAME = "sslHandler"; - private static final String FILE_REGION_ENCODER_NAME = "fileRegionEncoder"; + public static final String HANDSHAKE_HANDLER_NAME = "handshakeHandler"; + public static final String TLS_HANDLER_NAME = "sslHandler"; + public static final String FILE_REGION_ENCODER_NAME = "fileRegionEncoder"; // sharable handlers private HandshakeHandler handshakeHandler; private NettyEncoder encoder; private NettyConnectManageHandler connectionManageHandler; private NettyServerHandler serverHandler; + private RemotingCodeDistributionHandler distributionHandler; public NettyRemotingServer(final NettyServerConfig nettyServerConfig) { this(nettyServerConfig, null); @@ -101,61 +117,44 @@ public NettyRemotingServer(final NettyServerConfig nettyServerConfig, this.nettyServerConfig = nettyServerConfig; this.channelEventListener = channelEventListener; - int publicThreadNums = nettyServerConfig.getServerCallbackExecutorThreads(); - if (publicThreadNums <= 0) { - publicThreadNums = 4; - } + this.publicExecutor = buildPublicExecutor(nettyServerConfig); + this.scheduledExecutorService = buildScheduleExecutor(); - this.publicExecutor = Executors.newFixedThreadPool(publicThreadNums, new ThreadFactory() { - private AtomicInteger threadIndex = new AtomicInteger(0); + this.eventLoopGroupBoss = buildBossEventLoopGroup(); + this.eventLoopGroupSelector = buildEventLoopGroupSelector(); - @Override - public Thread newThread(Runnable r) { - return new Thread(r, "NettyServerPublicExecutor_" + this.threadIndex.incrementAndGet()); - } - }); + loadSslContext(); + } + private EventLoopGroup buildEventLoopGroupSelector() { if (useEpoll()) { - this.eventLoopGroupBoss = new EpollEventLoopGroup(1, new ThreadFactory() { - private AtomicInteger threadIndex = new AtomicInteger(0); - - @Override - public Thread newThread(Runnable r) { - return new Thread(r, String.format("NettyEPOLLBoss_%d", this.threadIndex.incrementAndGet())); - } - }); - - this.eventLoopGroupSelector = new EpollEventLoopGroup(nettyServerConfig.getServerSelectorThreads(), new ThreadFactory() { - private AtomicInteger threadIndex = new AtomicInteger(0); - private int threadTotal = nettyServerConfig.getServerSelectorThreads(); - - @Override - public Thread newThread(Runnable r) { - return new Thread(r, String.format("NettyServerEPOLLSelector_%d_%d", threadTotal, this.threadIndex.incrementAndGet())); - } - }); + return new EpollEventLoopGroup(nettyServerConfig.getServerSelectorThreads(), new ThreadFactoryImpl("NettyServerEPOLLSelector_")); } else { - this.eventLoopGroupBoss = new NioEventLoopGroup(1, new ThreadFactory() { - private AtomicInteger threadIndex = new AtomicInteger(0); - - @Override - public Thread newThread(Runnable r) { - return new Thread(r, String.format("NettyNIOBoss_%d", this.threadIndex.incrementAndGet())); - } - }); + return new NioEventLoopGroup(nettyServerConfig.getServerSelectorThreads(), new ThreadFactoryImpl("NettyServerNIOSelector_")); + } + } - this.eventLoopGroupSelector = new NioEventLoopGroup(nettyServerConfig.getServerSelectorThreads(), new ThreadFactory() { - private AtomicInteger threadIndex = new AtomicInteger(0); - private int threadTotal = nettyServerConfig.getServerSelectorThreads(); + private EventLoopGroup buildBossEventLoopGroup() { + if (useEpoll()) { + return new EpollEventLoopGroup(1, new ThreadFactoryImpl("NettyEPOLLBoss_")); + } else { + return new NioEventLoopGroup(1, new ThreadFactoryImpl("NettyNIOBoss_")); + } + } - @Override - public Thread newThread(Runnable r) { - return new Thread(r, String.format("NettyServerNIOSelector_%d_%d", threadTotal, this.threadIndex.incrementAndGet())); - } - }); + private ExecutorService buildPublicExecutor(NettyServerConfig nettyServerConfig) { + int publicThreadNums = nettyServerConfig.getServerCallbackExecutorThreads(); + if (publicThreadNums <= 0) { + publicThreadNums = 4; } - loadSslContext(); + return Executors.newFixedThreadPool(publicThreadNums, new ThreadFactoryImpl("NettyServerPublicExecutor_")); + } + + private ScheduledExecutorService buildScheduleExecutor() { + return new ScheduledThreadPoolExecutor(1, + new ThreadFactoryImpl("NettyServerScheduler_", true), + new ThreadPoolExecutor.DiscardOldestPolicy()); } public void loadSslContext() { @@ -166,58 +165,104 @@ public void loadSslContext() { try { sslContext = TlsHelper.buildSslContext(false); log.info("SSLContext created for server"); - } catch (CertificateException e) { - log.error("Failed to create SSLContext for server", e); - } catch (IOException e) { + } catch (CertificateException | IOException e) { log.error("Failed to create SSLContext for server", e); } } } private boolean useEpoll() { - return RemotingUtil.isLinuxPlatform() + return NetworkUtil.isLinuxPlatform() && nettyServerConfig.isUseEpollNativeSelector() && Epoll.isAvailable(); } @Override public void start() { - this.defaultEventExecutorGroup = new DefaultEventExecutorGroup( - nettyServerConfig.getServerWorkerThreads(), - new ThreadFactory() { + this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(nettyServerConfig.getServerWorkerThreads(), + new ThreadFactoryImpl("NettyServerCodecThread_")); - private AtomicInteger threadIndex = new AtomicInteger(0); + prepareSharableHandlers(); + 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 Thread newThread(Runnable r) { - return new Thread(r, "NettyServerCodecThread_" + this.threadIndex.incrementAndGet()); + public void initChannel(SocketChannel ch) { + configChannel(ch); } }); - prepareSharableHandlers(); + addCustomConfig(serverBootstrap); - ServerBootstrap childHandler = - this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector) - .channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class) - .option(ChannelOption.SO_BACKLOG, nettyServerConfig.getServerSocketBacklog()) - .option(ChannelOption.SO_REUSEADDR, true) - .option(ChannelOption.SO_KEEPALIVE, false) - .childOption(ChannelOption.TCP_NODELAY, true) - .localAddress(new InetSocketAddress(this.nettyServerConfig.getListenPort())) - .childHandler(new ChannelInitializer() { - @Override - public void initChannel(SocketChannel ch) throws Exception { - ch.pipeline() - .addLast(defaultEventExecutorGroup, HANDSHAKE_HANDLER_NAME, handshakeHandler) - .addLast(defaultEventExecutorGroup, - encoder, - new NettyDecoder(), - new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()), - connectionManageHandler, - serverHandler - ); - } - }); + 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(defaultEventExecutorGroup, HANDSHAKE_HANDLER_NAME, handshakeHandler) + .addLast(defaultEventExecutorGroup, + 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()); @@ -228,54 +273,26 @@ public void initChannel(SocketChannel ch) throws Exception { } if (nettyServerConfig.getWriteBufferLowWaterMark() > 0 && nettyServerConfig.getWriteBufferHighWaterMark() > 0) { log.info("server set netty WRITE_BUFFER_WATER_MARK to {},{}", - nettyServerConfig.getWriteBufferLowWaterMark(), nettyServerConfig.getWriteBufferHighWaterMark()); + nettyServerConfig.getWriteBufferLowWaterMark(), nettyServerConfig.getWriteBufferHighWaterMark()); childHandler.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark( - nettyServerConfig.getWriteBufferLowWaterMark(), nettyServerConfig.getWriteBufferHighWaterMark())); + nettyServerConfig.getWriteBufferLowWaterMark(), nettyServerConfig.getWriteBufferHighWaterMark())); } if (nettyServerConfig.isServerPooledByteBufAllocatorEnable()) { childHandler.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); } - - try { - ChannelFuture sync = this.serverBootstrap.bind().sync(); - InetSocketAddress addr = (InetSocketAddress) sync.channel().localAddress(); - this.port = addr.getPort(); - } catch (InterruptedException e1) { - throw new RuntimeException("this.serverBootstrap.bind().sync() InterruptedException", e1); - } - - if (this.channelEventListener != null) { - this.nettyEventExecutor.start(); - } - - this.timer.scheduleAtFixedRate(new TimerTask() { - - @Override - public void run() { - try { - NettyRemotingServer.this.scanResponseTable(); - } catch (Throwable e) { - log.error("scanResponseTable exception", e); - } - } - }, 1000 * 3, 1000); } @Override public void shutdown() { try { - if (this.timer != null) { - this.timer.cancel(); - } + this.timer.stop(); this.eventLoopGroupBoss.shutdownGracefully(); this.eventLoopGroupSelector.shutdownGracefully(); - if (this.nettyEventExecutor != null) { - this.nettyEventExecutor.shutdown(); - } + this.nettyEventExecutor.shutdown(); if (this.defaultEventExecutorGroup != null) { this.defaultEventExecutorGroup.shutdownGracefully(); @@ -293,13 +310,6 @@ public void shutdown() { } } - @Override - public void registerRPCHook(RPCHook rpcHook) { - if (rpcHook != null && !rpcHooks.contains(rpcHook)) { - rpcHooks.add(rpcHook); - } - } - @Override public void registerProcessor(int requestCode, NettyRequestProcessor processor, ExecutorService executor) { ExecutorService executorThis = executor; @@ -307,18 +317,18 @@ public void registerProcessor(int requestCode, NettyRequestProcessor processor, executorThis = this.publicExecutor; } - Pair pair = new Pair(processor, executorThis); + Pair pair = new Pair<>(processor, executorThis); this.processorTable.put(requestCode, pair); } @Override public void registerDefaultProcessor(NettyRequestProcessor processor, ExecutorService executor) { - this.defaultRequestProcessor = new Pair(processor, executor); + this.defaultRequestProcessorPair = new Pair<>(processor, executor); } @Override public int localListenPort() { - return this.port; + return this.nettyServerConfig.getListenPort(); } @Override @@ -326,6 +336,28 @@ public Pair getProcessorPair(int request 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 { @@ -349,7 +381,6 @@ public ChannelEventListener getChannelEventListener() { return channelEventListener; } - @Override public ExecutorService getCallbackExecutor() { return this.publicExecutor; @@ -360,10 +391,51 @@ private void prepareSharableHandlers() { encoder = new NettyEncoder(); connectionManageHandler = new NettyConnectManageHandler(); serverHandler = new NettyServerHandler(); + distributionHandler = new RemotingCodeDistributionHandler(); + } + + 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 defaultEventExecutorGroup; + } + + public HandshakeHandler getHandshakeHandler() { + return handshakeHandler; + } + + public NettyEncoder getEncoder() { + return encoder; + } + + public NettyConnectManageHandler getConnectionManageHandler() { + return connectionManageHandler; + } + + public NettyServerHandler getServerHandler() { + return serverHandler; + } + + public RemotingCodeDistributionHandler getDistributionHandler() { + return distributionHandler; } @ChannelHandler.Sharable - class HandshakeHandler extends SimpleChannelInboundHandler { + public class HandshakeHandler extends SimpleChannelInboundHandler { private final TlsMode tlsMode; @@ -374,12 +446,9 @@ class HandshakeHandler extends SimpleChannelInboundHandler { } @Override - protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { - - // mark the current position so that we can peek the first byte to determine if the content is starting with - // TLS handshake - msg.markReaderIndex(); + protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { + // Peek the first byte to determine if the content is starting with TLS handshake byte b = msg.getByte(0); if (b == HANDSHAKE_MAGIC_CODE) { @@ -410,9 +479,6 @@ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Excep log.warn("Clients intend to establish an insecure connection while this server is running in SSL enforcing mode"); } - // reset the reader index so that handshake negotiation may proceed as normal. - msg.resetReaderIndex(); - try { // Remove this handler ctx.pipeline().remove(this); @@ -426,16 +492,40 @@ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Excep } @ChannelHandler.Sharable - class NettyServerHandler extends SimpleChannelInboundHandler { + 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 - protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception { - processMessageReceived(ctx, msg); + 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 - class NettyConnectManageHandler extends ChannelDuplexHandler { + public class NettyConnectManageHandler extends ChannelDuplexHandler { @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel()); @@ -473,13 +563,13 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { } @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + 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); - RemotingUtil.closeChannel(ctx.channel()); + RemotingHelper.closeChannel(ctx.channel()); if (NettyRemotingServer.this.channelEventListener != null) { NettyRemotingServer.this .putNettyEvent(new NettyEvent(NettyEventType.IDLE, remoteAddress, ctx.channel())); @@ -491,7 +581,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc } @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + 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); @@ -500,7 +590,120 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E NettyRemotingServer.this.putNettyEvent(new NettyEvent(NettyEventType.EXCEPTION, remoteAddress, ctx.channel())); } - RemotingUtil.closeChannel(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() { + 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(); } } } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRequestProcessor.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRequestProcessor.java index 48006899c39..040f7684883 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRequestProcessor.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRequestProcessor.java @@ -27,5 +27,4 @@ RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand reques throws Exception; boolean rejectRequest(); - } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java index bd87e5b94e3..59ef2c84f15 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java @@ -17,7 +17,13 @@ package org.apache.rocketmq.remoting.netty; public class NettyServerConfig implements Cloneable { - private int listenPort = 8888; + + /** + * 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; @@ -33,7 +39,7 @@ public class NettyServerConfig implements Cloneable { private boolean serverPooledByteBufAllocatorEnable = true; /** - * make make install + * make install * * * ../glibc-2.10.1/configure \ --prefix=/usr \ --with-headers=/usr/include \ @@ -41,6 +47,14 @@ public class NettyServerConfig implements Cloneable { */ private boolean useEpollNativeSelector = false; + public String getBindAddress() { + return bindAddress; + } + + public void setBindAddress(String bindAddress) { + this.bindAddress = bindAddress; + } + public int getListenPort() { return listenPort; } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/RemotingCodeDistributionHandler.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/RemotingCodeDistributionHandler.java new file mode 100644 index 00000000000..c6a97fe441b --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/RemotingCodeDistributionHandler.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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)); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/RequestTask.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/RequestTask.java index 737ed7426d0..57ed3606090 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/RequestTask.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/RequestTask.java @@ -25,7 +25,7 @@ public class RequestTask implements Runnable { private final long createTimestamp = System.currentTimeMillis(); private final Channel channel; private final RemotingCommand request; - private boolean stopRun = false; + private volatile boolean stopRun = false; public RequestTask(final Runnable runnable, final Channel channel, final RemotingCommand request) { this.runnable = runnable; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/ResponseFuture.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/ResponseFuture.java index 5f4c8c69502..19f705d74bf 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/ResponseFuture.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/ResponseFuture.java @@ -25,8 +25,9 @@ import org.apache.rocketmq.remoting.protocol.RemotingCommand; public class ResponseFuture { + private final Channel channel; private final int opaque; - private final Channel processChannel; + private final RemotingCommand request; private final long timeoutMillis; private final InvokeCallback invokeCallback; private final long beginTimestamp = System.currentTimeMillis(); @@ -38,11 +39,18 @@ public class ResponseFuture { 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) { + 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.processChannel = channel; + this.request = request; this.timeoutMillis = timeoutMillis; this.invokeCallback = invokeCallback; this.once = once; @@ -56,6 +64,11 @@ public void executeInvokeCallback() { } } + public void interrupt() { + interrupted = true; + executeInvokeCallback(); + } + public void release() { if (this.once != null) { this.once.release(); @@ -117,20 +130,23 @@ public int getOpaque() { return opaque; } - public Channel getProcessChannel() { - return processChannel; + 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 - + ", processChannel=" + processChannel - + ", timeoutMillis=" + timeoutMillis - + ", invokeCallback=" + invokeCallback - + ", beginTimestamp=" + beginTimestamp + return "ResponseFuture [responseCommand=" + responseCommand + ", sendRequestOK=" + sendRequestOK + + ", cause=" + cause + ", opaque=" + opaque + ", timeoutMillis=" + timeoutMillis + + ", invokeCallback=" + invokeCallback + ", beginTimestamp=" + beginTimestamp + ", countDownLatch=" + countDownLatch + "]"; } } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/TlsHelper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/TlsHelper.java index 5003ce39efb..9e73ad7ae0a 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/TlsHelper.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/TlsHelper.java @@ -30,9 +30,9 @@ import java.io.InputStream; import java.security.cert.CertificateException; import java.util.Properties; -import org.apache.rocketmq.remoting.common.RemotingHelper; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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_CLIENT_AUTHSERVER; import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_CERTPATH; @@ -73,7 +73,7 @@ public interface DecryptionStrategy { InputStream decryptPrivateKey(String privateKeyEncryptPath, boolean forClient) throws IOException; } - private static final InternalLogger LOGGER = InternalLoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING); + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME); private static DecryptionStrategy decryptionStrategy = new DecryptionStrategy() { @Override diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/BitSetSerializerDeserializer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/BitSetSerializerDeserializer.java new file mode 100644 index 00000000000..8f53c0250bf --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/BitSetSerializerDeserializer.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.parser.DefaultJSONParser; +import com.alibaba.fastjson.parser.JSONToken; +import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer; +import com.alibaba.fastjson.serializer.JSONSerializer; +import com.alibaba.fastjson.serializer.ObjectSerializer; +import com.alibaba.fastjson.serializer.SerializeWriter; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.BitSet; + +public class BitSetSerializerDeserializer implements ObjectSerializer, ObjectDeserializer { + + @Override + public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException { + SerializeWriter out = serializer.out; + out.writeByteArray(((BitSet) object).toByteArray()); + } + + @SuppressWarnings("unchecked") + @Override + public T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { + byte[] bytes = parser.parseObject(byte[].class); + if (bytes != null) { + return (T) BitSet.valueOf(bytes); + } + return null; + } + + @Override + public int getFastMatchToken() { + return JSONToken.LITERAL_STRING; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/BrokerSyncInfo.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/BrokerSyncInfo.java new file mode 100644 index 00000000000..9340a70e6ee --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/BrokerSyncInfo.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 + + '}'; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/DataVersion.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/DataVersion.java similarity index 59% rename from common/src/main/java/org/apache/rocketmq/common/DataVersion.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/DataVersion.java index e54000deb65..655cf889bfa 100644 --- a/common/src/main/java/org/apache/rocketmq/common/DataVersion.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/DataVersion.java @@ -14,25 +14,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common; +package org.apache.rocketmq.remoting.protocol; import java.util.concurrent.atomic.AtomicLong; -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; 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; } @@ -56,22 +70,25 @@ public boolean equals(final Object o) { if (o == null || getClass() != o.getClass()) return false; - final DataVersion that = (DataVersion) o; + DataVersion version = (DataVersion) o; - if (timestamp != that.timestamp) { + if (getStateVersion() != version.getStateVersion()) + return false; + if (getTimestamp() != version.getTimestamp()) return false; - } - if (counter != null && that.counter != null) { - return counter.longValue() == that.counter.longValue(); + if (counter != null && version.counter != null) { + return counter.longValue() == version.counter.longValue(); } - return (null == counter) && (null == that.counter); + return null == counter && null == version.counter; + } @Override public int hashCode() { - int result = (int) (timestamp ^ (timestamp >>> 32)); + 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)); @@ -87,4 +104,21 @@ public String toString() { 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; + } } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/EpochEntry.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/EpochEntry.java new file mode 100644 index 00000000000..4ff81760adb --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/EpochEntry.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 { + + private int epoch; + private long startOffset; + private long endOffset = Long.MAX_VALUE; + + 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); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/FastCodesHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/FastCodesHeader.java new file mode 100644 index 00000000000..ebf9930889a --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/FastCodesHeader.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + + +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/ForbiddenType.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/ForbiddenType.java new file mode 100644 index 00000000000..0701dc57fc5 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/ForbiddenType.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 brocasting mode + */ + int BROADCASTING_DISABLE_FORBIDDEN = 4; + /** + * 5=forbidden for a substription(group with a topic) + */ + int SUBSCRIPTION_FORBIDDEN = 5; + +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/LanguageCode.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/LanguageCode.java index 4382af3511b..19280f99672 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/LanguageCode.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/LanguageCode.java @@ -29,7 +29,8 @@ public enum LanguageCode { HTTP((byte) 8), GO((byte) 9), PHP((byte) 10), - OMS((byte) 11); + OMS((byte) 11), + RUST((byte) 12); private byte code; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/MQProtosHelper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/MQProtosHelper.java similarity index 80% rename from common/src/main/java/org/apache/rocketmq/common/protocol/MQProtosHelper.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/MQProtosHelper.java index d8c1cedef14..918377f7e34 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/MQProtosHelper.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/MQProtosHelper.java @@ -15,17 +15,16 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol; +package org.apache.rocketmq.remoting.protocol; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.protocol.header.namesrv.RegisterBrokerRequestHeader; +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.header.namesrv.RegisterBrokerRequestHeader; public class MQProtosHelper { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); public static boolean registerBrokerToNameServer(final String nsaddr, final String brokerAddr, final long timeoutMillis) { diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/NamespaceUtil.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/NamespaceUtil.java similarity index 99% rename from common/src/main/java/org/apache/rocketmq/common/protocol/NamespaceUtil.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/NamespaceUtil.java index 60fadaab181..54016b392d4 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/NamespaceUtil.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/NamespaceUtil.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol; +package org.apache.rocketmq.remoting.protocol; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; @@ -170,4 +170,4 @@ public static boolean isRetryTopic(String resource) { public static boolean isDLQTopic(String resource) { return StringUtils.isNotBlank(resource) && resource.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX); } -} \ No newline at end of file +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingCommand.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingCommand.java index d43fe8ddadd..a6ed022eaeb 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingCommand.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingCommand.java @@ -17,33 +17,43 @@ package org.apache.rocketmq.remoting.protocol; import com.alibaba.fastjson.annotation.JSONField; +import com.google.common.base.Stopwatch; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; 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.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; + +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.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; -import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.exception.RemotingCommandException; 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"; - private static final InternalLogger log = InternalLoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING); + 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, Field[]>(); - private static final Map CANONICAL_NAME_CACHE = new HashMap(); + 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 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(); @@ -60,7 +70,7 @@ public class RemotingCommand { static { final String protocol = System.getProperty(SERIALIZE_TYPE_PROPERTY, System.getenv(SERIALIZE_TYPE_ENV)); - if (!isBlank(protocol)) { + if (!StringUtils.isBlank(protocol)) { try { serializeTypeConfigInThisServer = SerializeType.valueOf(protocol); } catch (IllegalArgumentException e) { @@ -81,6 +91,8 @@ public class RemotingCommand { private SerializeType serializeTypeCurrentRPC = serializeTypeConfigInThisServer; private transient byte[] body; + private boolean suspended; + private Stopwatch processTimer; protected RemotingCommand() { } @@ -93,7 +105,16 @@ public static RemotingCommand createRequestCommand(int code, CommandCustomHeader return cmd; } - private static void setCmdVersion(RemotingCommand 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 { @@ -110,6 +131,18 @@ public static RemotingCommand createResponseCommand(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(); @@ -120,12 +153,16 @@ public static RemotingCommand createResponseCommand(int code, String remark, if (classHeader != null) { try { - CommandCustomHeader objectHeader = classHeader.newInstance(); + 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; } } @@ -142,20 +179,24 @@ public static RemotingCommand decode(final byte[] array) throws RemotingCommandE } public static RemotingCommand decode(final ByteBuffer byteBuffer) throws RemotingCommandException { - int length = byteBuffer.limit(); - int oriHeaderLen = byteBuffer.getInt(); - int headerLength = getHeaderLength(oriHeaderLen); + return decode(Unpooled.wrappedBuffer(byteBuffer)); + } - byte[] headerData = new byte[headerLength]; - byteBuffer.get(headerData); + 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(headerData, getProtocolType(oriHeaderLen)); + RemotingCommand cmd = headerDecode(byteBuffer, headerLength, getProtocolType(oriHeaderLen)); int bodyLength = length - 4 - headerLength; byte[] bodyData = null; if (bodyLength > 0) { bodyData = new byte[bodyLength]; - byteBuffer.get(bodyData); + byteBuffer.readBytes(bodyData); } cmd.body = bodyData; @@ -166,14 +207,17 @@ public static int getHeaderLength(int length) { return length & 0xFFFFFF; } - private static RemotingCommand headerDecode(byte[] headerData, SerializeType type) throws RemotingCommandException { + 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(headerData); + RemotingCommand resultRMQ = RocketMQSerializable.rocketMQProtocolDecode(byteBuffer, len); resultRMQ.setSerializeTypeCurrentRPC(type); return resultRMQ; default: @@ -195,27 +239,8 @@ public static SerializeType getSerializeTypeConfigInThisServer() { return serializeTypeConfigInThisServer; } - private static boolean isBlank(String str) { - int strLen; - if (str == null || (strLen = str.length()) == 0) { - return true; - } - for (int i = 0; i < strLen; i++) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } - - public static byte[] markProtocolType(int source, SerializeType type) { - byte[] result = new byte[4]; - - result[0] = type.getCode(); - result[1] = (byte) ((source >> 16) & 0xFF); - result[2] = (byte) ((source >> 8) & 0xFF); - result[3] = (byte) (source & 0xFF); - return result; + public static int markProtocolType(int source, SerializeType type) { + return (type.getCode() << 24) | (source & 0x00FFFFFF); } public void markResponseType() { @@ -233,16 +258,30 @@ public void writeCustomHeader(CommandCustomHeader customHeader) { public CommandCustomHeader decodeCommandCustomHeader( Class classHeader) throws RemotingCommandException { + return decodeCommandCustomHeader(classHeader, true); + } + + public CommandCustomHeader decodeCommandCustomHeader(Class classHeader, + boolean useFastEncode) throws RemotingCommandException { CommandCustomHeader objectHeader; try { - objectHeader = classHeader.newInstance(); + objectHeader = classHeader.getDeclaredConstructor().newInstance(); } catch (InstantiationException e) { return null; } catch (IllegalAccessException e) { return null; + } catch (InvocationTargetException e) { + return null; + } catch (NoSuchMethodException 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) { @@ -291,11 +330,17 @@ public CommandCustomHeader decodeCommandCustomHeader( return objectHeader; } - private Field[] getClazzFields(Class classHeader) { + //make it able to test + Field[] getClazzFields(Class classHeader) { Field[] field = CLASS_HASH_MAP.get(classHeader); if (field == null) { - field = classHeader.getDeclaredFields(); + 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); } @@ -344,7 +389,7 @@ public ByteBuffer encode() { result.putInt(length); // header length - result.put(markProtocolType(headerData.length, serializeTypeCurrentRPC)); + result.putInt(markProtocolType(headerData.length, serializeTypeCurrentRPC)); // header data result.put(headerData); @@ -372,7 +417,7 @@ public void makeCustomHeaderToNet() { if (this.customHeader != null) { Field[] fields = getClazzFields(customHeader.getClass()); if (null == this.extFields) { - this.extFields = new HashMap(); + this.extFields = new HashMap<>(); } for (Field field : fields) { @@ -396,6 +441,27 @@ public void makeCustomHeaderToNet() { } } + 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); } @@ -419,12 +485,12 @@ public ByteBuffer encodeHeader(final int bodyLength) { result.putInt(length); // header length - result.put(markProtocolType(headerData.length, serializeTypeCurrentRPC)); + result.putInt(markProtocolType(headerData.length, serializeTypeCurrentRPC)); // header data result.put(headerData); - result.flip(); + ((Buffer) result).flip(); return result; } @@ -511,6 +577,16 @@ 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; } @@ -521,11 +597,15 @@ public void setExtFields(HashMap extFields) { public void addExtField(String key, String value) { if (null == extFields) { - extFields = new HashMap(); + 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)=" @@ -540,4 +620,12 @@ public SerializeType getSerializeTypeCurrentRPC() { public void setSerializeTypeCurrentRPC(SerializeType serializeTypeCurrentRPC) { this.serializeTypeCurrentRPC = serializeTypeCurrentRPC; } -} \ No newline at end of file + + public Stopwatch getProcessTimer() { + return processTimer; + } + + public void setProcessTimer(Stopwatch processTimer) { + this.processTimer = processTimer; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingSerializable.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingSerializable.java index f80ff14c107..60cc4e3e227 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingSerializable.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingSerializable.java @@ -17,17 +17,20 @@ package org.apache.rocketmq.remoting.protocol; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; + import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; public abstract class RemotingSerializable { - private final static Charset CHARSET_UTF8 = Charset.forName("UTF-8"); + private final static Charset CHARSET_UTF8 = StandardCharsets.UTF_8; public static byte[] encode(final Object obj) { - final String json = toJson(obj, false); - if (json != null) { - return json.getBytes(CHARSET_UTF8); + if (obj == null) { + return null; } - return null; + final String json = toJson(obj, false); + return json.getBytes(CHARSET_UTF8); } public static String toJson(final Object obj, boolean prettyFormat) { @@ -35,14 +38,17 @@ public static String toJson(final Object obj, boolean prettyFormat) { } public static T decode(final byte[] data, Class classOfT) { - final String json = new String(data, CHARSET_UTF8); - return fromJson(json, classOfT); + return fromJson(data, classOfT); } public static T fromJson(String json, Class classOfT) { return JSON.parseObject(json, classOfT); } + private static T fromJson(byte[] data, Class classOfT) { + return JSON.parseObject(data, classOfT); + } + public byte[] encode() { final String json = this.toJson(); if (json != null) { @@ -51,6 +57,17 @@ public byte[] encode() { return null; } + /** + * Allow call-site to apply specific features according to their requirements. + * + * @param features Features to apply + * @return serialized data. + */ + public byte[] encode(SerializerFeature...features) { + final String json = JSON.toJSONString(this, features); + return json.getBytes(CHARSET_UTF8); + } + public String toJson() { return toJson(false); } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/RequestCode.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java similarity index 67% rename from common/src/main/java/org/apache/rocketmq/common/protocol/RequestCode.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java index 5624a7ec01c..0b1a5e0104b 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/RequestCode.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol; +package org.apache.rocketmq.remoting.protocol; public class RequestCode { @@ -70,6 +70,8 @@ public class RequestCode { public static final int CHECK_CLIENT_CONFIG = 46; + public static final int GET_CLIENT_CONFIG = 47; + public static final int UPDATE_AND_CREATE_ACL_CONFIG = 50; public static final int DELETE_ACL_CONFIG = 51; @@ -80,6 +82,18 @@ public class RequestCode { public static final int GET_BROKER_CLUSTER_ACL_CONFIG = 54; + 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 PUT_KV_CONFIG = 100; public static final int GET_KV_CONFIG = 101; @@ -117,6 +131,7 @@ public class RequestCode { 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; @@ -131,6 +146,9 @@ public class RequestCode { public static final int GET_TOPICS_BY_CLUSTER = 224; + 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; @@ -190,4 +208,80 @@ public class RequestCode { 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_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 LITE_PULL_MESSAGE = 361; + + 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; + + public static final int GET_ALL_PRODUCER_INFO = 328; + + public static final int DELETE_EXPIRED_COMMITLOG = 329; + + /** + * 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 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 CONTROLLER_GET_NEXT_BROKER_ID = 1012; + + public static final int CONTROLLER_APPLY_BROKER_ID = 1013; } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestSource.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestSource.java new file mode 100644 index 00000000000..5d811601323 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestSource.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestType.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestType.java new file mode 100644 index 00000000000..65217d5b8de --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestType.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/ResponseCode.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/ResponseCode.java similarity index 59% rename from common/src/main/java/org/apache/rocketmq/common/protocol/ResponseCode.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/ResponseCode.java index dc744448f6c..e81dadf2e12 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/ResponseCode.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/ResponseCode.java @@ -15,9 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol; - -import org.apache.rocketmq.remoting.protocol.RemotingSysResponseCode; +package org.apache.rocketmq.remoting.protocol; public class ResponseCode extends RemotingSysResponseCode { @@ -80,4 +78,50 @@ public class ResponseCode extends RemotingSysResponseCode { public static final int UPDATE_GLOBAL_WHITE_ADDRS_CONFIG_FAILED = 211; + 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; + + /** + * 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; } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RocketMQSerializable.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RocketMQSerializable.java index 7c222bf4529..25ebbaafd94 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RocketMQSerializable.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RocketMQSerializable.java @@ -18,13 +18,81 @@ 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 = Charset.forName("UTF-8"); + 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 @@ -134,78 +202,43 @@ private static int calTotalLen(int remark, int ext) { return length; } - public static RemotingCommand rocketMQProtocolDecode(final byte[] headerArray) throws RemotingCommandException { + public static RemotingCommand rocketMQProtocolDecode(final ByteBuf headerBuffer, + int headerLen) throws RemotingCommandException { RemotingCommand cmd = new RemotingCommand(); - ByteBuffer headerBuffer = ByteBuffer.wrap(headerArray); // int code(~32767) - cmd.setCode(headerBuffer.getShort()); + cmd.setCode(headerBuffer.readShort()); // LanguageCode language - cmd.setLanguage(LanguageCode.valueOf(headerBuffer.get())); + cmd.setLanguage(LanguageCode.valueOf(headerBuffer.readByte())); // int version(~32767) - cmd.setVersion(headerBuffer.getShort()); + cmd.setVersion(headerBuffer.readShort()); // int opaque - cmd.setOpaque(headerBuffer.getInt()); + cmd.setOpaque(headerBuffer.readInt()); // int flag - cmd.setFlag(headerBuffer.getInt()); + cmd.setFlag(headerBuffer.readInt()); // String remark - int remarkLength = headerBuffer.getInt(); - if (remarkLength > 0) { - if (remarkLength > headerArray.length) { - throw new RemotingCommandException("RocketMQ protocol decoding failed, remark length: " + remarkLength + ", but header length: " + headerArray.length); - } - byte[] remarkContent = new byte[remarkLength]; - headerBuffer.get(remarkContent); - cmd.setRemark(new String(remarkContent, CHARSET_UTF8)); - } + cmd.setRemark(readStr(headerBuffer, false, headerLen)); // HashMap extFields - int extFieldsLength = headerBuffer.getInt(); + int extFieldsLength = headerBuffer.readInt(); if (extFieldsLength > 0) { - if (extFieldsLength > headerArray.length) { - throw new RemotingCommandException("RocketMQ protocol decoding failed, extFields length: " + extFieldsLength + ", but header length: " + headerArray.length); + if (extFieldsLength > headerLen) { + throw new RemotingCommandException("RocketMQ protocol decoding failed, extFields length: " + extFieldsLength + ", but header length: " + headerLen); } - byte[] extFieldsBytes = new byte[extFieldsLength]; - headerBuffer.get(extFieldsBytes); - cmd.setExtFields(mapDeserialize(extFieldsBytes)); + cmd.setExtFields(mapDeserialize(headerBuffer, extFieldsLength)); } return cmd; } - public static HashMap mapDeserialize(byte[] bytes) { - if (bytes == null || bytes.length <= 0) - return null; - - HashMap map = new HashMap(); - ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + public static HashMap mapDeserialize(ByteBuf byteBuffer, int len) throws RemotingCommandException { - short keySize; - byte[] keyContent; - int valSize; - byte[] valContent; - while (byteBuffer.hasRemaining()) { - keySize = byteBuffer.getShort(); - keyContent = new byte[keySize]; - byteBuffer.get(keyContent); + HashMap map = new HashMap<>(128); + int endIndex = byteBuffer.readerIndex() + len; - valSize = byteBuffer.getInt(); - valContent = new byte[valSize]; - byteBuffer.get(valContent); - - map.put(new String(keyContent, CHARSET_UTF8), new String(valContent, CHARSET_UTF8)); + while (byteBuffer.readerIndex() < endIndex) { + String k = readStr(byteBuffer, true, len); + String v = readStr(byteBuffer, false, len); + map.put(k, v); } return map; } - - public static boolean isBlank(String str) { - int strLen; - if (str == null || (strLen = str.length()) == 0) { - return true; - } - for (int i = 0; i < strLen; i++) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } } diff --git a/common/src/main/java/org/apache/rocketmq/common/admin/ConsumeStats.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/ConsumeStats.java similarity index 62% rename from common/src/main/java/org/apache/rocketmq/common/admin/ConsumeStats.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/ConsumeStats.java index 6b1c49290f9..1ddbfe9300b 100644 --- a/common/src/main/java/org/apache/rocketmq/common/admin/ConsumeStats.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/ConsumeStats.java @@ -14,36 +14,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.admin; +package org.apache.rocketmq.remoting.protocol.admin; -import java.util.HashMap; -import java.util.Iterator; +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 HashMap offsetTable = new HashMap(); + private Map offsetTable = new ConcurrentHashMap<>(); private double consumeTps = 0; public long computeTotalDiff() { long diffTotal = 0L; - - Iterator> it = this.offsetTable.entrySet().iterator(); - while (it.hasNext()) { - Entry next = it.next(); - long diff = next.getValue().getBrokerOffset() - next.getValue().getConsumerOffset(); - diffTotal += diff; + 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 HashMap getOffsetTable() { + public Map getOffsetTable() { return offsetTable; } - public void setOffsetTable(HashMap offsetTable) { + public void setOffsetTable(Map offsetTable) { this.offsetTable = offsetTable; } diff --git a/common/src/main/java/org/apache/rocketmq/common/admin/OffsetWrapper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/OffsetWrapper.java similarity index 85% rename from common/src/main/java/org/apache/rocketmq/common/admin/OffsetWrapper.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/OffsetWrapper.java index a246da82f3f..a6153617fd4 100644 --- a/common/src/main/java/org/apache/rocketmq/common/admin/OffsetWrapper.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/OffsetWrapper.java @@ -14,12 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.admin; +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() { @@ -38,6 +38,14 @@ 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; } diff --git a/common/src/main/java/org/apache/rocketmq/common/admin/RollbackStats.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/RollbackStats.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/admin/RollbackStats.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/RollbackStats.java index 4b9676e0331..467520749cb 100644 --- a/common/src/main/java/org/apache/rocketmq/common/admin/RollbackStats.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/RollbackStats.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.admin; +package org.apache.rocketmq.remoting.protocol.admin; public class RollbackStats { private String brokerName; diff --git a/common/src/main/java/org/apache/rocketmq/common/admin/TopicOffset.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/TopicOffset.java similarity index 82% rename from common/src/main/java/org/apache/rocketmq/common/admin/TopicOffset.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/TopicOffset.java index 7e667491836..be7eeeb40ae 100644 --- a/common/src/main/java/org/apache/rocketmq/common/admin/TopicOffset.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/TopicOffset.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.admin; +package org.apache.rocketmq.remoting.protocol.admin; public class TopicOffset { private long minOffset; @@ -44,4 +44,13 @@ public long getLastUpdateTimestamp() { public void setLastUpdateTimestamp(long lastUpdateTimestamp) { this.lastUpdateTimestamp = lastUpdateTimestamp; } + + @Override + public String toString() { + return "TopicOffset{" + + "minOffset=" + minOffset + + ", maxOffset=" + maxOffset + + ", lastUpdateTimestamp=" + lastUpdateTimestamp + + '}'; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/admin/TopicStatsTable.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/TopicStatsTable.java similarity index 75% rename from common/src/main/java/org/apache/rocketmq/common/admin/TopicStatsTable.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/TopicStatsTable.java index 729075c061a..9f467e7449e 100644 --- a/common/src/main/java/org/apache/rocketmq/common/admin/TopicStatsTable.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/admin/TopicStatsTable.java @@ -14,20 +14,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.admin; +package org.apache.rocketmq.remoting.protocol.admin; -import java.util.HashMap; +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 HashMap offsetTable = new HashMap(); + private Map offsetTable = new ConcurrentHashMap<>(); - public HashMap getOffsetTable() { + public Map getOffsetTable() { return offsetTable; } - public void setOffsetTable(HashMap offsetTable) { + public void setOffsetTable(Map offsetTable) { this.offsetTable = offsetTable; } } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BatchAck.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BatchAck.java new file mode 100644 index 00000000000..82dcd8567ea --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BatchAck.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.fastjson.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 + + '}'; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BatchAckMessageRequestBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BatchAckMessageRequestBody.java new file mode 100644 index 00000000000..f0e1a8c3c8d --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BatchAckMessageRequestBody.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerMemberGroup.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerMemberGroup.java new file mode 100644 index 00000000000..32497fadc78 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerMemberGroup.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 + + '}'; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerReplicasInfo.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerReplicasInfo.java new file mode 100644 index 00000000000..a2960165ed7 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerReplicasInfo.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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); + } + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/BrokerStatsData.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerStatsData.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/BrokerStatsData.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerStatsData.java index c4ff63d0b89..f6649aa9738 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/BrokerStatsData.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerStatsData.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/BrokerStatsItem.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerStatsItem.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/BrokerStatsItem.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerStatsItem.java index e3246d0e6f7..1a339adc770 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/BrokerStatsItem.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/BrokerStatsItem.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; public class BrokerStatsItem { private long sum; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/CMResult.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/CMResult.java similarity index 94% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/CMResult.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/CMResult.java index d6dc94382d9..3e25402f0ca 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/CMResult.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/CMResult.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; public enum CMResult { CR_SUCCESS, diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/CheckClientRequestBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/CheckClientRequestBody.java similarity index 83% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/CheckClientRequestBody.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/CheckClientRequestBody.java index a78ce554560..bd482d07468 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/CheckClientRequestBody.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/CheckClientRequestBody.java @@ -15,16 +15,17 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; 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; @@ -49,4 +50,12 @@ public SubscriptionData getSubscriptionData() { public void setSubscriptionData(SubscriptionData subscriptionData) { this.subscriptionData = subscriptionData; } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ClusterAclVersionInfo.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ClusterAclVersionInfo.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ClusterAclVersionInfo.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ClusterAclVersionInfo.java index 27c55de3f45..3ec6cf64f32 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ClusterAclVersionInfo.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ClusterAclVersionInfo.java @@ -14,12 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; - -import org.apache.rocketmq.common.DataVersion; -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; +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; public class ClusterAclVersionInfo extends RemotingSerializable { diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ClusterInfo.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ClusterInfo.java similarity index 61% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ClusterInfo.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ClusterInfo.java index 76c64a8508c..2ee73018b1b 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ClusterInfo.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ClusterInfo.java @@ -15,37 +15,38 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; +import com.google.common.base.Objects; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; -import org.apache.rocketmq.common.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; +import org.apache.rocketmq.remoting.protocol.route.BrokerData; public class ClusterInfo extends RemotingSerializable { - private HashMap brokerAddrTable; - private HashMap> clusterAddrTable; + private Map brokerAddrTable; + private Map> clusterAddrTable; - public HashMap getBrokerAddrTable() { + public Map getBrokerAddrTable() { return brokerAddrTable; } - public void setBrokerAddrTable(HashMap brokerAddrTable) { + public void setBrokerAddrTable(Map brokerAddrTable) { this.brokerAddrTable = brokerAddrTable; } - public HashMap> getClusterAddrTable() { + public Map> getClusterAddrTable() { return clusterAddrTable; } - public void setClusterAddrTable(HashMap> clusterAddrTable) { + public void setClusterAddrTable(Map> clusterAddrTable) { this.clusterAddrTable = clusterAddrTable; } public String[] retrieveAllAddrByCluster(String cluster) { - List addrs = new ArrayList(); + List addrs = new ArrayList<>(); if (clusterAddrTable.containsKey(cluster)) { Set brokerNames = clusterAddrTable.get(cluster); for (String brokerName : brokerNames) { @@ -62,4 +63,19 @@ public String[] retrieveAllAddrByCluster(String cluster) { 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); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/Connection.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/Connection.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/Connection.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/Connection.java index b42737f88c5..2e804243db4 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/Connection.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/Connection.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.LanguageCode; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeByWho.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeByWho.java similarity index 90% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeByWho.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeByWho.java index 7b20d760e64..ad62493d712 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeByWho.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeByWho.java @@ -14,14 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +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 HashSet consumedGroup = new HashSet<>(); + private HashSet notConsumedGroup = new HashSet<>(); private String topic; private int queueId; private long offset; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeMessageDirectlyResult.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeMessageDirectlyResult.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeMessageDirectlyResult.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeMessageDirectlyResult.java index 674df60229d..39da734a682 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeMessageDirectlyResult.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeMessageDirectlyResult.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeQueueData.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeQueueData.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeQueueData.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeQueueData.java index 7268dcda56b..34ebc9af378 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeQueueData.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeQueueData.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; public class ConsumeQueueData { diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeStatsList.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeStatsList.java similarity index 80% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeStatsList.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeStatsList.java index 7b35a8099df..11b36c86976 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeStatsList.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeStatsList.java @@ -14,18 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.apache.rocketmq.common.admin.ConsumeStats; 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 List>> consumeStatsList = new ArrayList<>(); private String brokerAddr; private long totalDiff; + private long totalInflightDiff; public List>> getConsumeStatsList() { return consumeStatsList; @@ -50,4 +51,12 @@ public long getTotalDiff() { public void setTotalDiff(long totalDiff) { this.totalDiff = totalDiff; } + + public long getTotalInflightDiff() { + return totalInflightDiff; + } + + public void setTotalInflightDiff(long totalInflightDiff) { + this.totalInflightDiff = totalInflightDiff; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeStatus.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeStatus.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeStatus.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeStatus.java index 5e9a3cc88e5..6f4729cd298 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumeStatus.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumeStatus.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; public class ConsumeStatus { private double pullRT; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerConnection.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumerConnection.java similarity index 87% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerConnection.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumerConnection.java index 3a0356c7cc9..4eb5d7da4ef 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerConnection.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumerConnection.java @@ -15,21 +15,21 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +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.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; 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 HashSet connectionSet = new HashSet<>(); private ConcurrentMap subscriptionTable = - new ConcurrentHashMap(); + new ConcurrentHashMap<>(); private ConsumeType consumeType; private MessageModel messageModel; private ConsumeFromWhere consumeFromWhere; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerOffsetSerializeWrapper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumerOffsetSerializeWrapper.java similarity index 79% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerOffsetSerializeWrapper.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumerOffsetSerializeWrapper.java index 5b08d788d9e..407be4670e8 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerOffsetSerializeWrapper.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumerOffsetSerializeWrapper.java @@ -15,15 +15,17 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +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); + new ConcurrentHashMap<>(512); + private DataVersion dataVersion; public ConcurrentMap> getOffsetTable() { return offsetTable; @@ -32,4 +34,12 @@ public ConcurrentMap> getOffsetTable() { public void setOffsetTable(ConcurrentMap> offsetTable) { this.offsetTable = offsetTable; } + + public DataVersion getDataVersion() { + return dataVersion; + } + + public void setDataVersion(DataVersion dataVersion) { + this.dataVersion = dataVersion; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerRunningInfo.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumerRunningInfo.java similarity index 78% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerRunningInfo.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumerRunningInfo.java index 0c3df0d5a87..542f9300678 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerRunningInfo.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ConsumerRunningInfo.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import java.util.Iterator; import java.util.Map.Entry; @@ -23,9 +23,9 @@ import java.util.TreeMap; import java.util.TreeSet; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; 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"; @@ -37,26 +37,22 @@ public class ConsumerRunningInfo extends RemotingSerializable { private Properties properties = new Properties(); - private TreeSet subscriptionSet = new TreeSet(); + private TreeSet subscriptionSet = new TreeSet<>(); - private TreeMap mqTable = new TreeMap(); + private TreeMap mqTable = new TreeMap<>(); - private TreeMap statusTable = 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 = false; - { - String property = prev.getProperties().getProperty(ConsumerRunningInfo.PROP_CONSUME_TYPE); - - if (property == null) { - property = ((ConsumeType) prev.getProperties().get(ConsumerRunningInfo.PROP_CONSUME_TYPE)).name(); - } - push = ConsumeType.valueOf(property) == ConsumeType.CONSUME_PASSIVELY; - } + boolean push = isPushType(prev); boolean startForAWhile = false; { @@ -85,19 +81,29 @@ public static boolean analyzeSubscription(final TreeMap criTable) { return true; } @@ -135,7 +141,7 @@ public static String analyzeProcessQueue(final String clientId, ConsumerRunningI mq, System.currentTimeMillis() - pq.getLastLockTimestamp())); } else { - if (pq.isDroped() && (pq.getTryUnlockTimes() > 0)) { + if (pq.isDroped() && pq.getTryUnlockTimes() > 0) { sb.append(String.format("%s %s unlock %d times, still failed%n", clientId, mq, @@ -191,6 +197,10 @@ public void setStatusTable(TreeMap statusTable) { this.statusTable = statusTable; } + public TreeMap getUserConsumerInfo() { + return userConsumerInfo; + } + public String formatString() { StringBuilder sb = new StringBuilder(); @@ -265,6 +275,28 @@ public String formatString() { } } + { + 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", @@ -294,6 +326,16 @@ public String formatString() { } } + 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); @@ -310,4 +352,12 @@ public void setJstack(String jstack) { this.jstack = jstack; } + public TreeMap getMqPopTable() { + return mqPopTable; + } + + public void setMqPopTable( + TreeMap mqPopTable) { + this.mqPopTable = mqPopTable; + } } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ElectMasterResponseBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ElectMasterResponseBody.java new file mode 100644 index 00000000000..8aef636fa4c --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ElectMasterResponseBody.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/EpochEntryCache.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/EpochEntryCache.java new file mode 100644 index 00000000000..642331cb11f --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/EpochEntryCache.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 + + '}'; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetBrokerMemberGroupResponseBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetBrokerMemberGroupResponseBody.java new file mode 100644 index 00000000000..f3384022060 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetBrokerMemberGroupResponseBody.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/GetConsumerStatusBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetConsumerStatusBody.java similarity index 92% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/GetConsumerStatusBody.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetConsumerStatusBody.java index 6234a774d4e..f69193ad10a 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/GetConsumerStatusBody.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetConsumerStatusBody.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import java.util.HashMap; import java.util.Map; @@ -24,9 +24,9 @@ @Deprecated public class GetConsumerStatusBody extends RemotingSerializable { - private Map messageQueueTable = new HashMap(); + private Map messageQueueTable = new HashMap<>(); private Map> consumerTable = - new HashMap>(); + new HashMap<>(); public Map getMessageQueueTable() { return messageQueueTable; diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionStore.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetRemoteClientConfigBody.java similarity index 63% rename from broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionStore.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetRemoteClientConfigBody.java index 03e02273934..6aa5470476f 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionStore.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetRemoteClientConfigBody.java @@ -14,29 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.apache.rocketmq.remoting.protocol.body; -package org.apache.rocketmq.broker.transaction; - +import java.util.ArrayList; import java.util.List; +import org.apache.rocketmq.remoting.protocol.RemotingSerializable; -/** - * This class will be removed in ther version 4.4.0, and {@link TransactionalMessageService} class is recommended. - */ -@Deprecated -public interface TransactionStore { - boolean open(); - - void close(); - - boolean put(final List trs); - - void remove(final List pks); - - List traverse(final long pk, final int nums); - - long totalRecords(); +public class GetRemoteClientConfigBody extends RemotingSerializable { + private List keys = new ArrayList<>(); - long minPK(); + public List getKeys() { + return keys; + } - long maxPK(); + public void setKeys(List keys) { + this.keys = keys; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/GroupList.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GroupList.java similarity index 90% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/GroupList.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GroupList.java index 862a739ec6a..f2fa43fc944 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/GroupList.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GroupList.java @@ -14,13 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +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(); + private HashSet groupList = new HashSet<>(); public HashSet getGroupList() { return groupList; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/HARuntimeInfo.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/HARuntimeInfo.java new file mode 100644 index 00000000000..d0f3fb231a3 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/HARuntimeInfo.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } + } + +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/KVTable.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/KVTable.java similarity index 89% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/KVTable.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/KVTable.java index 28aafc45782..73452b4c925 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/KVTable.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/KVTable.java @@ -14,13 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +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(); + private HashMap table = new HashMap<>(); public HashMap getTable() { return table; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/LockBatchRequestBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/LockBatchRequestBody.java similarity index 82% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/LockBatchRequestBody.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/LockBatchRequestBody.java index 480862b8f47..02912446cfd 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/LockBatchRequestBody.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/LockBatchRequestBody.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import java.util.HashSet; import java.util.Set; @@ -25,7 +25,8 @@ public class LockBatchRequestBody extends RemotingSerializable { private String consumerGroup; private String clientId; - private Set mqSet = new HashSet(); + private boolean onlyThisBroker = false; + private Set mqSet = new HashSet<>(); public String getConsumerGroup() { return consumerGroup; @@ -43,6 +44,14 @@ 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; } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/LockBatchResponseBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/LockBatchResponseBody.java similarity index 90% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/LockBatchResponseBody.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/LockBatchResponseBody.java index 5018f203146..a46a8aac37f 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/LockBatchResponseBody.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/LockBatchResponseBody.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import java.util.HashSet; import java.util.Set; @@ -24,7 +24,7 @@ public class LockBatchResponseBody extends RemotingSerializable { - private Set lockOKMQSet = new HashSet(); + private Set lockOKMQSet = new HashSet<>(); public Set getLockOKMQSet() { return lockOKMQSet; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/MessageRequestModeSerializeWrapper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/MessageRequestModeSerializeWrapper.java new file mode 100644 index 00000000000..fcc0e6f893a --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/MessageRequestModeSerializeWrapper.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/PopProcessQueueInfo.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/PopProcessQueueInfo.java new file mode 100644 index 00000000000..3a6bc3f69e4 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/PopProcessQueueInfo.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 + "]"; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ProcessQueueInfo.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProcessQueueInfo.java similarity index 98% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ProcessQueueInfo.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProcessQueueInfo.java index 6b220b81216..075b56eb820 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ProcessQueueInfo.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProcessQueueInfo.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.common.UtilAll; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ProducerConnection.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProducerConnection.java similarity index 90% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ProducerConnection.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProducerConnection.java index 14d71104b27..91efe5a2326 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ProducerConnection.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProducerConnection.java @@ -15,13 +15,13 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +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(); + private HashSet connectionSet = new HashSet<>(); public HashSet getConnectionSet() { return connectionSet; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProducerInfo.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProducerInfo.java new file mode 100644 index 00000000000..bb6d3c8c738 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProducerInfo.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProducerTableInfo.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProducerTableInfo.java new file mode 100644 index 00000000000..d4a1d0b3d84 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ProducerTableInfo.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryAssignmentRequestBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryAssignmentRequestBody.java new file mode 100644 index 00000000000..fc83b511340 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryAssignmentRequestBody.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryAssignmentResponseBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryAssignmentResponseBody.java new file mode 100644 index 00000000000..8d9b53270bb --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryAssignmentResponseBody.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryConsumeQueueResponseBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryConsumeQueueResponseBody.java similarity index 94% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryConsumeQueueResponseBody.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryConsumeQueueResponseBody.java index be93da993d0..ecc84c6e8a6 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryConsumeQueueResponseBody.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryConsumeQueueResponseBody.java @@ -15,12 +15,11 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; - -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; +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 { diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryConsumeTimeSpanBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryConsumeTimeSpanBody.java similarity index 90% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryConsumeTimeSpanBody.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryConsumeTimeSpanBody.java index cae21094bbb..599ccc890f8 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryConsumeTimeSpanBody.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryConsumeTimeSpanBody.java @@ -15,14 +15,14 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +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(); + List consumeTimeSpanSet = new ArrayList<>(); public List getConsumeTimeSpanSet() { return consumeTimeSpanSet; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryCorrectionOffsetBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryCorrectionOffsetBody.java similarity index 90% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryCorrectionOffsetBody.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryCorrectionOffsetBody.java index f7c866ac084..85be8bc9325 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryCorrectionOffsetBody.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueryCorrectionOffsetBody.java @@ -14,14 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +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(); + private Map correctionOffsets = new HashMap<>(); public Map getCorrectionOffsets() { return correctionOffsets; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QuerySubscriptionResponseBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QuerySubscriptionResponseBody.java new file mode 100644 index 00000000000..e094a07234f --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QuerySubscriptionResponseBody.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueueTimeSpan.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueueTimeSpan.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/QueueTimeSpan.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueueTimeSpan.java index e579d9162e9..6bcb2a38f93 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueueTimeSpan.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/QueueTimeSpan.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import java.util.Date; import org.apache.rocketmq.common.UtilAll; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/RegisterBrokerBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/RegisterBrokerBody.java similarity index 68% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/RegisterBrokerBody.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/RegisterBrokerBody.java index 4065c087922..99557b1d3fb 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/RegisterBrokerBody.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/RegisterBrokerBody.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import com.alibaba.fastjson.JSON; import java.io.ByteArrayInputStream; @@ -30,19 +30,22 @@ import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; -import org.apache.rocketmq.common.DataVersion; +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.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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; public class RegisterBrokerBody extends RemotingSerializable { - private static final InternalLogger LOGGER = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); - private TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); - private List filterServerList = new ArrayList(); + 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) { @@ -82,10 +85,24 @@ public byte[] encode(boolean compress) { // 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 interval = System.currentTimeMillis() - start; - if (interval > 50) { - LOGGER.info("Compressing takes {}ms", interval); + long takeTime = System.currentTimeMillis() - start; + if (takeTime > MINIMUM_TAKE_TIME_MILLISECOND) { + LOGGER.info("Compressing takes {}ms", takeTime); } return byteArrayOutputStream.toByteArray(); } catch (IOException e) { @@ -95,7 +112,7 @@ public byte[] encode(boolean compress) { return null; } - public static RegisterBrokerBody decode(byte[] data, boolean compressed) throws IOException { + public static RegisterBrokerBody decode(byte[] data, boolean compressed, MQVersion.Version brokerVersion) throws IOException { if (!compressed) { return RegisterBrokerBody.decode(data, RegisterBrokerBody.class); } @@ -126,7 +143,7 @@ public static RegisterBrokerBody decode(byte[] data, boolean compressed) throws byte[] filterServerListBuffer = readBytes(inflaterInputStream, filterServerListJsonLength); String filterServerListJson = new String(filterServerListBuffer, MixAll.DEFAULT_CHARSET); - List filterServerList = new ArrayList(); + List filterServerList = new ArrayList<>(); try { filterServerList = JSON.parseArray(filterServerListJson, String.class); } catch (Exception e) { @@ -134,9 +151,22 @@ public static RegisterBrokerBody decode(byte[] data, boolean compressed) throws } registerBrokerBody.setFilterServerList(filterServerList); - long interval = System.currentTimeMillis() - start; - if (interval > 50) { - LOGGER.info("Decompressing takes {}ms", interval); + + 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; } @@ -167,11 +197,11 @@ private static int readInt(InflaterInputStream inflaterInputStream) throws IOExc return byteBuffer.getInt(); } - public TopicConfigSerializeWrapper getTopicConfigSerializeWrapper() { + public TopicConfigAndMappingSerializeWrapper getTopicConfigSerializeWrapper() { return topicConfigSerializeWrapper; } - public void setTopicConfigSerializeWrapper(TopicConfigSerializeWrapper topicConfigSerializeWrapper) { + public void setTopicConfigSerializeWrapper(TopicConfigAndMappingSerializeWrapper topicConfigSerializeWrapper) { this.topicConfigSerializeWrapper = topicConfigSerializeWrapper; } @@ -183,15 +213,13 @@ public void setFilterServerList(List filterServerList) { this.filterServerList = filterServerList; } - public static ConcurrentMap cloneTopicConfigTable( + private ConcurrentMap cloneTopicConfigTable( ConcurrentMap topicConfigConcurrentMap) { - ConcurrentHashMap result = new ConcurrentHashMap(); - if (topicConfigConcurrentMap != null) { - for (Map.Entry entry : topicConfigConcurrentMap.entrySet()) { - result.put(entry.getKey(), entry.getValue()); - } + if (topicConfigConcurrentMap == null) { + return null; } + ConcurrentHashMap result = new ConcurrentHashMap<>(topicConfigConcurrentMap.size()); + result.putAll(topicConfigConcurrentMap); return result; - } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ResetOffsetBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ResetOffsetBody.java similarity index 89% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ResetOffsetBody.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ResetOffsetBody.java index b28e74b5656..840bfbf40db 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ResetOffsetBody.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ResetOffsetBody.java @@ -15,15 +15,21 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +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; } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ResetOffsetBodyForC.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ResetOffsetBodyForC.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/ResetOffsetBodyForC.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ResetOffsetBodyForC.java index fa812ed2cd9..24702328c97 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ResetOffsetBodyForC.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/ResetOffsetBodyForC.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import java.util.List; import org.apache.rocketmq.common.message.MessageQueueForC; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/RoleChangeNotifyEntry.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/RoleChangeNotifyEntry.java new file mode 100644 index 00000000000..ab25df0d1d0 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/RoleChangeNotifyEntry.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SetMessageRequestModeRequestBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SetMessageRequestModeRequestBody.java new file mode 100644 index 00000000000..31aecd0ba63 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SetMessageRequestModeRequestBody.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/SubscriptionGroupWrapper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SubscriptionGroupWrapper.java similarity index 87% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/SubscriptionGroupWrapper.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SubscriptionGroupWrapper.java index e05f75961ff..7c159021aae 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/SubscriptionGroupWrapper.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SubscriptionGroupWrapper.java @@ -15,17 +15,17 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.apache.rocketmq.common.DataVersion; -import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig; +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); + new ConcurrentHashMap<>(1024); private DataVersion dataVersion = new DataVersion(); public ConcurrentMap getSubscriptionGroupTable() { diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SyncStateSet.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SyncStateSet.java new file mode 100644 index 00000000000..f0a71f8a978 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/SyncStateSet.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 + + '}'; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicConfigAndMappingSerializeWrapper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicConfigAndMappingSerializeWrapper.java new file mode 100644 index 00000000000..ae9a193ebb0 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicConfigAndMappingSerializeWrapper.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/TopicConfigSerializeWrapper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicConfigSerializeWrapper.java similarity index 91% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/TopicConfigSerializeWrapper.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicConfigSerializeWrapper.java index ce123021d45..b42a5b90a43 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/TopicConfigSerializeWrapper.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicConfigSerializeWrapper.java @@ -15,17 +15,17 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.apache.rocketmq.common.DataVersion; 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(); + new ConcurrentHashMap<>(); private DataVersion dataVersion = new DataVersion(); public ConcurrentMap getTopicConfigTable() { diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/TopicList.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicList.java similarity index 88% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/TopicList.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicList.java index baf8312479f..30edfb5a987 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/TopicList.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicList.java @@ -14,14 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; -import java.util.HashSet; import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class TopicList extends RemotingSerializable { - private Set topicList = new HashSet(); + private Set topicList = new CopyOnWriteArraySet<>(); private String brokerAddr; public Set getTopicList() { diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicQueueMappingSerializeWrapper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicQueueMappingSerializeWrapper.java new file mode 100644 index 00000000000..17e16b8402f --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/TopicQueueMappingSerializeWrapper.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/UnlockBatchRequestBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/UnlockBatchRequestBody.java similarity index 82% rename from common/src/main/java/org/apache/rocketmq/common/protocol/body/UnlockBatchRequestBody.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/UnlockBatchRequestBody.java index baf40713065..fcac7ed9ae9 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/UnlockBatchRequestBody.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/UnlockBatchRequestBody.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import java.util.HashSet; import java.util.Set; @@ -25,7 +25,8 @@ public class UnlockBatchRequestBody extends RemotingSerializable { private String consumerGroup; private String clientId; - private Set mqSet = new HashSet(); + private boolean onlyThisBroker = false; + private Set mqSet = new HashSet<>(); public String getConsumerGroup() { return consumerGroup; @@ -43,6 +44,14 @@ 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; } diff --git a/common/src/main/java/org/apache/rocketmq/common/filter/FilterAPI.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/filter/FilterAPI.java similarity index 54% rename from common/src/main/java/org/apache/rocketmq/common/filter/FilterAPI.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/filter/FilterAPI.java index c5b51b1aff5..10a6bb46333 100644 --- a/common/src/main/java/org/apache/rocketmq/common/filter/FilterAPI.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/filter/FilterAPI.java @@ -14,50 +14,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.filter; +package org.apache.rocketmq.remoting.protocol.filter; -import java.net.URL; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; +import org.apache.commons.lang3.StringUtils; +import org.apache.rocketmq.common.filter.ExpressionType; +import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; -public class FilterAPI { - public static URL classFile(final String className) { - final String javaSource = simpleClassName(className) + ".java"; - URL url = FilterAPI.class.getClassLoader().getResource(javaSource); - return url; - } - - public static String simpleClassName(final String className) { - String simple = className; - int index = className.lastIndexOf("."); - if (index >= 0) { - simple = className.substring(index + 1); - } +import java.util.Arrays; - return simple; - } +public class FilterAPI { public static SubscriptionData buildSubscriptionData(String topic, String subString) throws Exception { - SubscriptionData subscriptionData = new SubscriptionData(); + final SubscriptionData subscriptionData = new SubscriptionData(); subscriptionData.setTopic(topic); subscriptionData.setSubString(subString); - if (null == subString || subString.equals(SubscriptionData.SUB_ALL) || subString.length() == 0) { + 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 { - String[] tags = subString.split("\\|\\|"); - if (tags.length > 0) { - for (String tag : tags) { - if (tag.length() > 0) { - String trimString = tag.trim(); - if (trimString.length() > 0) { - subscriptionData.getTagsSet().add(trimString); - subscriptionData.getCodeSet().add(trimString.hashCode()); - } - } - } - } else { - throw new Exception("subString split error"); - } + throw new Exception("subString split error"); } return subscriptionData; @@ -69,7 +52,7 @@ public static SubscriptionData build(final String topic, final String subString, return buildSubscriptionData(topic, subString); } - if (subString == null || subString.length() < 1) { + if (StringUtils.isEmpty(subString)) { throw new IllegalArgumentException("Expression can't be null! " + type); } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/AckMessageRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/AckMessageRequestHeader.java new file mode 100644 index 00000000000..9c5d4d8b1c7 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/AckMessageRequestHeader.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; +import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; + +public class AckMessageRequestHeader extends TopicQueueRequestHeader { + @CFNotNull + private String consumerGroup; + @CFNotNull + private String topic; + @CFNotNull + private Integer queueId; + @CFNotNull + private String extraInfo; + + @CFNotNull + private Long offset; + + + @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; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("consumerGroup", consumerGroup) + .add("topic", topic) + .add("queueId", queueId) + .add("extraInfo", extraInfo) + .add("offset", offset) + .toString(); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/AddBrokerRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/AddBrokerRequestHeader.java new file mode 100644 index 00000000000..8ec19833323 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/AddBrokerRequestHeader.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ChangeInvisibleTimeRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ChangeInvisibleTimeRequestHeader.java new file mode 100644 index 00000000000..fd63de0fb7e --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ChangeInvisibleTimeRequestHeader.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; +import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; + +public class ChangeInvisibleTimeRequestHeader extends TopicQueueRequestHeader { + @CFNotNull + private String consumerGroup; + @CFNotNull + private String topic; + @CFNotNull + private Integer queueId; + /** + * startOffset popTime invisibleTime queueId + */ + @CFNotNull + private String extraInfo; + + @CFNotNull + private Long offset; + + @CFNotNull + private Long invisibleTime; + + @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; + } + + @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) + .toString(); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ChangeInvisibleTimeResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ChangeInvisibleTimeResponseHeader.java new file mode 100644 index 00000000000..c3b1cca6da2 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ChangeInvisibleTimeResponseHeader.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CheckTransactionStateRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CheckTransactionStateRequestHeader.java similarity index 78% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/CheckTransactionStateRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CheckTransactionStateRequestHeader.java index 6cba71c7e91..8c04eaa2e2c 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CheckTransactionStateRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CheckTransactionStateRequestHeader.java @@ -18,13 +18,14 @@ /** * $Id: EndTransactionRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; -import org.apache.rocketmq.remoting.CommandCustomHeader; +import com.google.common.base.MoreObjects; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; +import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; -public class CheckTransactionStateRequestHeader implements CommandCustomHeader { +public class CheckTransactionStateRequestHeader extends RpcRequestHeader { @CFNotNull private Long tranStateTableOffset; @CFNotNull @@ -76,4 +77,15 @@ public String getOffsetMsgId() { 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(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CheckTransactionStateResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CheckTransactionStateResponseHeader.java similarity index 98% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/CheckTransactionStateResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CheckTransactionStateResponseHeader.java index d429eed724a..9aa2d7addba 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CheckTransactionStateResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CheckTransactionStateResponseHeader.java @@ -18,7 +18,7 @@ /** * $Id: EndTransactionResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.common.sysflag.MessageSysFlag; import org.apache.rocketmq.remoting.CommandCustomHeader; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CloneGroupOffsetRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CloneGroupOffsetRequestHeader.java similarity index 84% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/CloneGroupOffsetRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CloneGroupOffsetRequestHeader.java index afc017b2a6c..a9e9982af10 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CloneGroupOffsetRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CloneGroupOffsetRequestHeader.java @@ -18,8 +18,9 @@ /** * $Id: DeleteTopicRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; +import com.google.common.base.MoreObjects; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; @@ -68,4 +69,14 @@ public boolean isOffline() { 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(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumeMessageDirectlyResultRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ConsumeMessageDirectlyResultRequestHeader.java similarity index 64% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumeMessageDirectlyResultRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ConsumeMessageDirectlyResultRequestHeader.java index 1bd089d7a7b..de9a4a50128 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumeMessageDirectlyResultRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ConsumeMessageDirectlyResultRequestHeader.java @@ -15,8 +15,9 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; +import com.google.common.base.MoreObjects; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; @@ -31,6 +32,12 @@ public class ConsumeMessageDirectlyResultRequestHeader implements CommandCustomH private String msgId; @CFNullable private String brokerName; + @CFNullable + private String topic; + @CFNullable + private Integer topicSysFlag; + @CFNullable + private Integer groupSysFlag; @Override public void checkFields() throws RemotingCommandException { @@ -67,4 +74,41 @@ public String getMsgId() { 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(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumerSendMsgBackRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ConsumerSendMsgBackRequestHeader.java similarity index 81% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumerSendMsgBackRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ConsumerSendMsgBackRequestHeader.java index bd8fbb44ca0..f69e016c2c9 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumerSendMsgBackRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ConsumerSendMsgBackRequestHeader.java @@ -15,14 +15,15 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; -import org.apache.rocketmq.remoting.CommandCustomHeader; +import com.google.common.base.MoreObjects; 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; -public class ConsumerSendMsgBackRequestHeader implements CommandCustomHeader { +public class ConsumerSendMsgBackRequestHeader extends RpcRequestHeader { @CFNotNull private Long offset; @CFNotNull @@ -98,7 +99,14 @@ public void setMaxReconsumeTimes(final Integer maxReconsumeTimes) { @Override public String toString() { - return "ConsumerSendMsgBackRequestHeader [group=" + group + ", originTopic=" + originTopic + ", originMsgId=" + originMsgId - + ", delayLevel=" + delayLevel + ", unitMode=" + unitMode + ", maxReconsumeTimes=" + maxReconsumeTimes + "]"; + 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(); } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateAccessConfigRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CreateAccessConfigRequestHeader.java similarity index 81% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateAccessConfigRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CreateAccessConfigRequestHeader.java index 36990fcf641..d02a23858d1 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateAccessConfigRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CreateAccessConfigRequestHeader.java @@ -15,8 +15,9 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; +import com.google.common.base.MoreObjects; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; @@ -41,9 +42,10 @@ public class CreateAccessConfigRequestHeader implements CommandCustomHeader { // list string,eg: groupD=DENY,groupD=SUB private String groupPerms; - - @Override public void checkFields() throws RemotingCommandException { + + @Override + public void checkFields() throws RemotingCommandException { } @@ -110,4 +112,18 @@ public String getGroupPerms() { public void setGroupPerms(String groupPerms) { this.groupPerms = groupPerms; } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("accessKey", accessKey) + .add("secretKey", secretKey) + .add("whiteRemoteAddress", whiteRemoteAddress) + .add("admin", admin) + .add("defaultTopicPerm", defaultTopicPerm) + .add("defaultGroupPerm", defaultGroupPerm) + .add("topicPerms", topicPerms) + .add("groupPerms", groupPerms) + .toString(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateTopicRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CreateTopicRequestHeader.java similarity index 75% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateTopicRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CreateTopicRequestHeader.java index 8894d0b938f..b68c5197ee8 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateTopicRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/CreateTopicRequestHeader.java @@ -18,11 +18,13 @@ /** * $Id: CreateTopicRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; +import com.google.common.base.MoreObjects; import org.apache.rocketmq.common.TopicFilterType; 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; public class CreateTopicRequestHeader implements CommandCustomHeader { @@ -41,6 +43,10 @@ public class CreateTopicRequestHeader implements CommandCustomHeader { private Integer topicSysFlag; @CFNotNull private Boolean order = false; + private String attributes; + + @CFNullable + private Boolean force = false; @Override public void checkFields() throws RemotingCommandException { @@ -118,4 +124,36 @@ public Boolean getOrder() { 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(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/DeleteAccessConfigRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteAccessConfigRequestHeader.java similarity index 90% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/DeleteAccessConfigRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteAccessConfigRequestHeader.java index 293480ce321..ef5cbdc9f1a 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/DeleteAccessConfigRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteAccessConfigRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; @@ -26,7 +26,8 @@ public class DeleteAccessConfigRequestHeader implements CommandCustomHeader { @CFNotNull private String accessKey; - @Override public void checkFields() throws RemotingCommandException { + @Override + public void checkFields() throws RemotingCommandException { } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/DeleteSubscriptionGroupRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteSubscriptionGroupRequestHeader.java similarity index 84% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/DeleteSubscriptionGroupRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteSubscriptionGroupRequestHeader.java index 6591d7780cd..26126f77432 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/DeleteSubscriptionGroupRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteSubscriptionGroupRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; @@ -25,7 +25,7 @@ public class DeleteSubscriptionGroupRequestHeader implements CommandCustomHeader @CFNotNull private String groupName; - private boolean removeOffset; + private boolean cleanOffset = false; @Override public void checkFields() throws RemotingCommandException { @@ -39,11 +39,11 @@ public void setGroupName(String groupName) { this.groupName = groupName; } - public boolean isRemoveOffset() { - return removeOffset; + public boolean isCleanOffset() { + return cleanOffset; } - public void setRemoveOffset(boolean removeOffset) { - this.removeOffset = removeOffset; + public void setCleanOffset(boolean cleanOffset) { + this.cleanOffset = cleanOffset; } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/DeleteTopicRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteTopicRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/DeleteTopicRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteTopicRequestHeader.java index 54dd7f87b4a..1305a70cc15 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/DeleteTopicRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/DeleteTopicRequestHeader.java @@ -18,7 +18,7 @@ /** * $Id: DeleteTopicRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/EndTransactionRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/EndTransactionRequestHeader.java similarity index 83% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/EndTransactionRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/EndTransactionRequestHeader.java index 87661c320ad..3f5515e272e 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/EndTransactionRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/EndTransactionRequestHeader.java @@ -15,15 +15,16 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; +import com.google.common.base.MoreObjects; 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.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; +import org.apache.rocketmq.remoting.rpc.RpcRequestHeader; -public class EndTransactionRequestHeader implements CommandCustomHeader { +public class EndTransactionRequestHeader extends RpcRequestHeader { @CFNotNull private String producerGroup; @CFNotNull @@ -118,14 +119,14 @@ public void setTransactionId(String transactionId) { @Override public String toString() { - return "EndTransactionRequestHeader{" + - "producerGroup='" + producerGroup + '\'' + - ", tranStateTableOffset=" + tranStateTableOffset + - ", commitLogOffset=" + commitLogOffset + - ", commitOrRollback=" + commitOrRollback + - ", fromTransactionCheck=" + fromTransactionCheck + - ", msgId='" + msgId + '\'' + - ", transactionId='" + transactionId + '\'' + - '}'; + 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(); } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/EndTransactionResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/EndTransactionResponseHeader.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/EndTransactionResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/EndTransactionResponseHeader.java index 1b01a6c65a9..f6aa51e877d 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/EndTransactionResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/EndTransactionResponseHeader.java @@ -18,7 +18,7 @@ /** * $Id: EndTransactionResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExchangeHAInfoRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExchangeHAInfoRequestHeader.java new file mode 100644 index 00000000000..b636e36ec95 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExchangeHAInfoRequestHeader.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExchangeHAInfoResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExchangeHAInfoResponseHeader.java new file mode 100644 index 00000000000..3bbbc4cc49a --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExchangeHAInfoResponseHeader.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExtraInfoUtil.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExtraInfoUtil.java new file mode 100644 index 00000000000..9a5fa89abab --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExtraInfoUtil.java @@ -0,0 +1,296 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.HashMap; +import java.util.List; +import java.util.Map; +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"; + public static final String RETRY_TOPIC = "1"; + 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.buildPopRetryTopic(topic, cid); + } else { + return topic; + } + } + + public static String getRealTopic(String topic, String cid, boolean isRetry) { + return isRetry ? KeyBuilder.buildPopRetryTopic(topic, cid) : topic; + } + + 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 = NORMAL_TOPIC; + if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { + t = RETRY_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 = NORMAL_TOPIC; + if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { + t = RETRY_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, boolean retry, int queueId, long startOffset) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(64); + } + + if (stringBuilder.length() > 0) { + stringBuilder.append(";"); + } + + stringBuilder.append(retry ? RETRY_TOPIC : NORMAL_TOPIC) + .append(MessageConst.KEY_SEPARATOR).append(queueId) + .append(MessageConst.KEY_SEPARATOR).append(startOffset); + } + + public static void buildQueueIdOrderCountInfo(StringBuilder stringBuilder, boolean retry, int queueId, int orderCount) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(64); + } + + if (stringBuilder.length() > 0) { + stringBuilder.append(";"); + } + + stringBuilder.append(retry ? RETRY_TOPIC : NORMAL_TOPIC) + .append(MessageConst.KEY_SEPARATOR).append(queueId) + .append(MessageConst.KEY_SEPARATOR).append(orderCount); + } + + public static void buildQueueOffsetOrderCountInfo(StringBuilder stringBuilder, boolean retry, long queueId, long queueOffset, int orderCount) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(64); + } + + if (stringBuilder.length() > 0) { + stringBuilder.append(";"); + } + + stringBuilder.append(retry ? RETRY_TOPIC : NORMAL_TOPIC) + .append(MessageConst.KEY_SEPARATOR).append(getQueueOffsetKeyValueKey(queueId, queueOffset)) + .append(MessageConst.KEY_SEPARATOR).append(orderCount); + } + + public static void buildMsgOffsetInfo(StringBuilder stringBuilder, boolean retry, int queueId, List msgOffsets) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(64); + } + + if (stringBuilder.length() > 0) { + stringBuilder.append(";"); + } + + stringBuilder.append(retry ? RETRY_TOPIC : NORMAL_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 String getStartOffsetInfoMapKey(String topic, long key) { + return (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) ? RETRY_TOPIC : NORMAL_TOPIC) + "@" + 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 (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) ? RETRY_TOPIC : NORMAL_TOPIC) + "@" + getQueueOffsetKeyValueKey(queueId, queueOffset); + } + + public static boolean isOrder(String[] extraInfo) { + return ExtraInfoUtil.getReviveQid(extraInfo) == KeyBuilder.POP_ORDER_REVIVE_QUEUE; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetAllProducerInfoRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetAllProducerInfoRequestHeader.java new file mode 100644 index 00000000000..a24de24fd9d --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetAllProducerInfoRequestHeader.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 GetAllProducerInfoRequestHeader implements CommandCustomHeader { + @Override + public void checkFields() throws RemotingCommandException { + // To change body of implemented methods use File | Settings | File + // Templates. + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetAllTopicConfigResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetAllTopicConfigResponseHeader.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetAllTopicConfigResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetAllTopicConfigResponseHeader.java index ea477e848ea..cd8da68c60c 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetAllTopicConfigResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetAllTopicConfigResponseHeader.java @@ -18,7 +18,7 @@ /** * $Id: GetAllTopicConfigResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetBrokerAclConfigResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerAclConfigResponseHeader.java similarity index 94% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetBrokerAclConfigResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerAclConfigResponseHeader.java index 70c6d5e2020..50f5713b8b5 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetBrokerAclConfigResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerAclConfigResponseHeader.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; @@ -36,7 +36,8 @@ public class GetBrokerAclConfigResponseHeader implements CommandCustomHeader { @CFNotNull private String clusterName; - @Override public void checkFields() throws RemotingCommandException { + @Override + public void checkFields() throws RemotingCommandException { } public String getVersion() { diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetBrokerClusterAclConfigResponseBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerClusterAclConfigResponseBody.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetBrokerClusterAclConfigResponseBody.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerClusterAclConfigResponseBody.java index 10ea210c822..4987242c2a9 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetBrokerClusterAclConfigResponseBody.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerClusterAclConfigResponseBody.java @@ -15,13 +15,12 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; +import java.util.List; import org.apache.rocketmq.common.PlainAccessConfig; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; -import java.util.List; - public class GetBrokerClusterAclConfigResponseBody extends RemotingSerializable { private List globalWhiteAddrs; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetBrokerClusterAclConfigResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerClusterAclConfigResponseHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetBrokerClusterAclConfigResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerClusterAclConfigResponseHeader.java index dbff54a0e8d..7de73aa4daa 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetBrokerClusterAclConfigResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerClusterAclConfigResponseHeader.java @@ -15,15 +15,14 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; +import java.util.List; import org.apache.rocketmq.common.PlainAccessConfig; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; -import java.util.List; - public class GetBrokerClusterAclConfigResponseHeader implements CommandCustomHeader { @CFNotNull diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetBrokerConfigResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerConfigResponseHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetBrokerConfigResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerConfigResponseHeader.java index bc30342fccb..bcc6721ef82 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetBrokerConfigResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerConfigResponseHeader.java @@ -18,7 +18,7 @@ /** * $Id: GetBrokerConfigResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/filtersrv/RegisterFilterServerResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerMemberGroupRequestHeader.java similarity index 76% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/filtersrv/RegisterFilterServerResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerMemberGroupRequestHeader.java index edfe7b70da5..964025f5e6f 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/filtersrv/RegisterFilterServerResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetBrokerMemberGroupRequestHeader.java @@ -15,35 +15,37 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.filtersrv; +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 RegisterFilterServerResponseHeader implements CommandCustomHeader { +public class GetBrokerMemberGroupRequestHeader implements CommandCustomHeader { @CFNotNull - private String brokerName; - @CFNotNull - private long brokerId; + private String clusterName; - @Override - public void checkFields() throws RemotingCommandException { - } + @CFNotNull + private String brokerName; - public long getBrokerId() { - return brokerId; + public String getClusterName() { + return clusterName; } - public void setBrokerId(long brokerId) { - this.brokerId = brokerId; + public void setClusterName(final String clusterName) { + this.clusterName = clusterName; } public String getBrokerName() { return brokerName; } - public void setBrokerName(String brokerName) { + public void setBrokerName(final String brokerName) { this.brokerName = brokerName; } + + @Override + public void checkFields() throws RemotingCommandException { + + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumeStatsInBrokerHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumeStatsInBrokerHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumeStatsInBrokerHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumeStatsInBrokerHeader.java index b18e8c52e61..156f6dbefd0 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumeStatsInBrokerHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumeStatsInBrokerHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumeStatsRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumeStatsRequestHeader.java similarity index 83% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumeStatsRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumeStatsRequestHeader.java index 6ba069e1ffd..901de85b100 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumeStatsRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumeStatsRequestHeader.java @@ -14,8 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; +import com.google.common.base.MoreObjects; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; @@ -44,4 +45,12 @@ public String getTopic() { public void setTopic(String topic) { this.topic = topic; } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("consumerGroup", consumerGroup) + .add("topic", topic) + .toString(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerConnectionListRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerConnectionListRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerConnectionListRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerConnectionListRequestHeader.java index 8f7579660f7..7ade0c16738 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerConnectionListRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerConnectionListRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerListByGroupRequestHeader.java similarity index 83% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerListByGroupRequestHeader.java index 3523a52caee..e16331a3f1e 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerListByGroupRequestHeader.java @@ -15,8 +15,9 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; +import com.google.common.base.MoreObjects; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; @@ -36,4 +37,11 @@ public String getConsumerGroup() { public void setConsumerGroup(String consumerGroup) { this.consumerGroup = consumerGroup; } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("consumerGroup", consumerGroup) + .toString(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupResponseBody.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerListByGroupResponseBody.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupResponseBody.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerListByGroupResponseBody.java index da39e7725e0..545ea12f752 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupResponseBody.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerListByGroupResponseBody.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import java.util.List; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerListByGroupResponseHeader.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerListByGroupResponseHeader.java index 2eb41031fff..42ca5f1fd50 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerListByGroupResponseHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerRunningInfoRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerRunningInfoRequestHeader.java similarity index 84% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerRunningInfoRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerRunningInfoRequestHeader.java index 1bbbd900c53..2adad968e5e 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerRunningInfoRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerRunningInfoRequestHeader.java @@ -15,8 +15,9 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; +import com.google.common.base.MoreObjects; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; @@ -57,4 +58,13 @@ public boolean isJstackEnable() { 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(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerStatusRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerStatusRequestHeader.java similarity index 84% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerStatusRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerStatusRequestHeader.java index ca26a869c60..9aee3d4ae88 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerStatusRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetConsumerStatusRequestHeader.java @@ -15,8 +15,9 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; +import com.google.common.base.MoreObjects; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.annotation.CFNullable; @@ -57,4 +58,13 @@ public String getClientAddr() { 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(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetEarliestMsgStoretimeRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetEarliestMsgStoretimeRequestHeader.java similarity index 85% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetEarliestMsgStoretimeRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetEarliestMsgStoretimeRequestHeader.java index c64381fb787..b6a3a2d47dd 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetEarliestMsgStoretimeRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetEarliestMsgStoretimeRequestHeader.java @@ -18,13 +18,13 @@ /** * $Id: GetEarliestMsgStoretimeRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +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; +import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; -public class GetEarliestMsgStoretimeRequestHeader implements CommandCustomHeader { +public class GetEarliestMsgStoretimeRequestHeader extends TopicQueueRequestHeader { @CFNotNull private String topic; @CFNotNull @@ -34,18 +34,22 @@ public class GetEarliestMsgStoretimeRequestHeader implements CommandCustomHeader 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; } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetEarliestMsgStoretimeResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetEarliestMsgStoretimeResponseHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetEarliestMsgStoretimeResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetEarliestMsgStoretimeResponseHeader.java index 6b9b3b21d20..94983527a26 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetEarliestMsgStoretimeResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetEarliestMsgStoretimeResponseHeader.java @@ -18,7 +18,7 @@ /** * $Id: GetEarliestMsgStoretimeResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMaxOffsetRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMaxOffsetRequestHeader.java new file mode 100644 index 00000000000..ec4219874a6 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMaxOffsetRequestHeader.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.annotation.CFNullable; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; +import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; + +public class GetMaxOffsetRequestHeader extends TopicQueueRequestHeader { + @CFNotNull + 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(); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMaxOffsetResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMaxOffsetResponseHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMaxOffsetResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMaxOffsetResponseHeader.java index fcd0a302fd8..1e2f6872063 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMaxOffsetResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMaxOffsetResponseHeader.java @@ -18,7 +18,7 @@ /** * $Id: GetMaxOffsetResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMinOffsetRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMinOffsetRequestHeader.java similarity index 75% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMinOffsetRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMinOffsetRequestHeader.java index 6fb8ed40c45..11087e6730f 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMinOffsetRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMinOffsetRequestHeader.java @@ -18,13 +18,14 @@ /** * $Id: GetMinOffsetRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; -import org.apache.rocketmq.remoting.CommandCustomHeader; +import com.google.common.base.MoreObjects; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; +import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; -public class GetMinOffsetRequestHeader implements CommandCustomHeader { +public class GetMinOffsetRequestHeader extends TopicQueueRequestHeader { @CFNotNull private String topic; @CFNotNull @@ -34,19 +35,31 @@ public class GetMinOffsetRequestHeader implements CommandCustomHeader { 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(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMinOffsetResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMinOffsetResponseHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMinOffsetResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMinOffsetResponseHeader.java index 6fc0fac3b05..b4e1ae92ac5 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMinOffsetResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetMinOffsetResponseHeader.java @@ -18,7 +18,7 @@ /** * $Id: GetMinOffsetResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetProducerConnectionListRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetProducerConnectionListRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetProducerConnectionListRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetProducerConnectionListRequestHeader.java index 880c4e41c38..2b919e02451 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetProducerConnectionListRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetProducerConnectionListRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetSubscriptionGroupConfigRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetSubscriptionGroupConfigRequestHeader.java new file mode 100644 index 00000000000..885ab256da2 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetSubscriptionGroupConfigRequestHeader.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.remoting.CommandCustomHeader; +import org.apache.rocketmq.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; + +public class GetSubscriptionGroupConfigRequestHeader implements CommandCustomHeader { + + @Override + public void checkFields() throws RemotingCommandException { + } + + @CFNotNull + 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetTopicConfigRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetTopicConfigRequestHeader.java new file mode 100644 index 00000000000..69b07f188fc --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetTopicConfigRequestHeader.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.TopicRequestHeader; + +public class GetTopicConfigRequestHeader extends TopicRequestHeader { + @Override + public void checkFields() throws RemotingCommandException { + } + + @CFNotNull + 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetTopicStatsInfoRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetTopicStatsInfoRequestHeader.java similarity index 86% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetTopicStatsInfoRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetTopicStatsInfoRequestHeader.java index c4cf4decdcb..7d1e8d6b7d9 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetTopicStatsInfoRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetTopicStatsInfoRequestHeader.java @@ -15,13 +15,13 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +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; +import org.apache.rocketmq.remoting.rpc.TopicRequestHeader; -public class GetTopicStatsInfoRequestHeader implements CommandCustomHeader { +public class GetTopicStatsInfoRequestHeader extends TopicRequestHeader { @CFNotNull private String topic; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetTopicsByClusterRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetTopicsByClusterRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetTopicsByClusterRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetTopicsByClusterRequestHeader.java index 9955e71ed6c..08af6f875c9 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetTopicsByClusterRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/GetTopicsByClusterRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/InitConsumerOffsetRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/InitConsumerOffsetRequestHeader.java new file mode 100644 index 00000000000..e6d319bca60 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/InitConsumerOffsetRequestHeader.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 InitConsumerOffsetRequestHeader implements CommandCustomHeader { + + 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotificationRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotificationRequestHeader.java new file mode 100644 index 00000000000..5965e9dcbb4 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotificationRequestHeader.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 NotificationRequestHeader extends TopicQueueRequestHeader { + @CFNotNull + private String consumerGroup; + @CFNotNull + private String topic; + @CFNotNull + private int queueId; + @CFNotNull + private long pollTime; + @CFNotNull + private long bornTime; + + private Boolean order = Boolean.FALSE; + private String attemptId; + + @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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/filtersrv/RegisterFilterServerRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotificationResponseHeader.java similarity index 75% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/filtersrv/RegisterFilterServerRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotificationResponseHeader.java index 0b5ac1e13c0..cbab5974015 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/filtersrv/RegisterFilterServerRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotificationResponseHeader.java @@ -14,26 +14,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.apache.rocketmq.common.protocol.header.filtersrv; +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 RegisterFilterServerRequestHeader implements CommandCustomHeader { +public class NotificationResponseHeader implements CommandCustomHeader { + + @CFNotNull - private String filterServerAddr; + private boolean hasMsg = false; - @Override - public void checkFields() throws RemotingCommandException { + public boolean isHasMsg() { + return hasMsg; } - public String getFilterServerAddr() { - return filterServerAddr; + public void setHasMsg(boolean hasMsg) { + this.hasMsg = hasMsg; } - public void setFilterServerAddr(String filterServerAddr) { - this.filterServerAddr = filterServerAddr; + @Override + public void checkFields() throws RemotingCommandException { } } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyBrokerRoleChangedRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyBrokerRoleChangedRequestHeader.java new file mode 100644 index 00000000000..3a112a57824 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyBrokerRoleChangedRequestHeader.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 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 { + + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/NotifyConsumerIdsChangedRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyConsumerIdsChangedRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/NotifyConsumerIdsChangedRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyConsumerIdsChangedRequestHeader.java index b9c1a176537..40ee9417f18 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/NotifyConsumerIdsChangedRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyConsumerIdsChangedRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyMinBrokerIdChangeRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyMinBrokerIdChangeRequestHeader.java new file mode 100644 index 00000000000..2b02006abcb --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/NotifyMinBrokerIdChangeRequestHeader.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/filtersrv/RegisterMessageFilterClassRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PeekMessageRequestHeader.java similarity index 72% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/filtersrv/RegisterMessageFilterClassRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PeekMessageRequestHeader.java index e9c0e46fc8d..a6f23dc2ee0 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/filtersrv/RegisterMessageFilterClassRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PeekMessageRequestHeader.java @@ -14,22 +14,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.apache.rocketmq.remoting.protocol.header; -package org.apache.rocketmq.common.protocol.header.filtersrv; - -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.rpc.TopicQueueRequestHeader; -public class RegisterMessageFilterClassRequestHeader implements CommandCustomHeader { - @CFNotNull - private String consumerGroup; +public class PeekMessageRequestHeader extends TopicQueueRequestHeader { @CFNotNull private String topic; @CFNotNull - private String className; + private int queueId; + @CFNotNull + private int maxMsgNums; @CFNotNull - private Integer classCRC; + private String consumerGroup; @Override public void checkFields() throws RemotingCommandException { @@ -51,19 +50,21 @@ public void setTopic(String topic) { this.topic = topic; } - public String getClassName() { - return className; + public Integer getQueueId() { + return queueId; } - public void setClassName(String className) { - this.className = className; + public void setQueueId(Integer queueId) { + this.queueId = queueId; } - public Integer getClassCRC() { - return classCRC; + + public int getMaxMsgNums() { + return maxMsgNums; } - public void setClassCRC(Integer classCRC) { - this.classCRC = classCRC; + public void setMaxMsgNums(int maxMsgNums) { + this.maxMsgNums = maxMsgNums; } + } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PollingInfoRequestHeader.java similarity index 59% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PollingInfoRequestHeader.java index 6834881ec8a..558fb3f5061 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PollingInfoRequestHeader.java @@ -15,57 +15,50 @@ * limitations under the License. */ -/** - * $Id: SendMessageResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ - */ -package org.apache.rocketmq.common.protocol.header; +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; +import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; + -public class SendMessageResponseHeader implements CommandCustomHeader { +public class PollingInfoRequestHeader extends TopicQueueRequestHeader { @CFNotNull - private String msgId; + private String consumerGroup; @CFNotNull - private Integer queueId; + private String topic; @CFNotNull - private Long queueOffset; - private String transactionId; + private int queueId; @Override public void checkFields() throws RemotingCommandException { } - public String getMsgId() { - return msgId; + public String getConsumerGroup() { + return consumerGroup; } - public void setMsgId(String msgId) { - this.msgId = msgId; + public void setConsumerGroup(String consumerGroup) { + this.consumerGroup = consumerGroup; } - public Integer getQueueId() { - return queueId; + public String getTopic() { + return topic; } - public void setQueueId(Integer queueId) { - this.queueId = queueId; + public void setTopic(String topic) { + this.topic = topic; } - public Long getQueueOffset() { - return queueOffset; - } - - public void setQueueOffset(Long queueOffset) { - this.queueOffset = queueOffset; + public Integer getQueueId() { + if (queueId < 0) { + return -1; + } + return queueId; } - public String getTransactionId() { - return transactionId; + public void setQueueId(Integer queueId) { + this.queueId = queueId; } - public void setTransactionId(String transactionId) { - this.transactionId = transactionId; - } } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PollingInfoResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PollingInfoResponseHeader.java new file mode 100644 index 00000000000..7d2d852dfb1 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PollingInfoResponseHeader.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 { + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PopMessageRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PopMessageRequestHeader.java new file mode 100644 index 00000000000..34b97987ddd --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PopMessageRequestHeader.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; +import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; + +public class PopMessageRequestHeader extends TopicQueueRequestHeader { + @CFNotNull + private String consumerGroup; + @CFNotNull + 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(); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PopMessageResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PopMessageResponseHeader.java new file mode 100644 index 00000000000..da17733abe1 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PopMessageResponseHeader.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PullMessageRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PullMessageRequestHeader.java new file mode 100644 index 00000000000..a6d6d3b64c3 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PullMessageRequestHeader.java @@ -0,0 +1,329 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.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; + +public class PullMessageRequestHeader extends TopicQueueRequestHeader implements FastCodesHeader { + + @CFNotNull + private String consumerGroup; + @CFNotNull + private String topic; + @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, "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 = 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; + } + + @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("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(); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PullMessageResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PullMessageResponseHeader.java new file mode 100644 index 00000000000..bc356f2e9e1 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/PullMessageResponseHeader.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryConsumeQueueRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumeQueueRequestHeader.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryConsumeQueueRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumeQueueRequestHeader.java index 642fe17cb63..53cc2a1f55f 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryConsumeQueueRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumeQueueRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryConsumeTimeSpanRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumeTimeSpanRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryConsumeTimeSpanRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumeTimeSpanRequestHeader.java index 5250d8bd41d..370f0160535 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryConsumeTimeSpanRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumeTimeSpanRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryConsumerOffsetRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumerOffsetRequestHeader.java similarity index 77% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryConsumerOffsetRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumerOffsetRequestHeader.java index 3b7f627c35a..39aaa011762 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryConsumerOffsetRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumerOffsetRequestHeader.java @@ -18,13 +18,13 @@ /** * $Id: QueryConsumerOffsetRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +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; +import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; -public class QueryConsumerOffsetRequestHeader implements CommandCustomHeader { +public class QueryConsumerOffsetRequestHeader extends TopicQueueRequestHeader { @CFNotNull private String consumerGroup; @CFNotNull @@ -32,6 +32,8 @@ public class QueryConsumerOffsetRequestHeader implements CommandCustomHeader { @CFNotNull private Integer queueId; + private Boolean setZeroIfNotFound; + @Override public void checkFields() throws RemotingCommandException { } @@ -44,19 +46,31 @@ 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; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryConsumerOffsetResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumerOffsetResponseHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryConsumerOffsetResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumerOffsetResponseHeader.java index c21710a318f..1ee706fd00f 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryConsumerOffsetResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryConsumerOffsetResponseHeader.java @@ -18,7 +18,7 @@ /** * $Id: QueryConsumerOffsetResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryCorrectionOffsetHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryCorrectionOffsetHeader.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryCorrectionOffsetHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryCorrectionOffsetHeader.java index 93fa7227420..51099cb5751 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryCorrectionOffsetHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryCorrectionOffsetHeader.java @@ -18,7 +18,7 @@ /** * $Id: GetMinOffsetRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryMessageRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryMessageRequestHeader.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryMessageRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryMessageRequestHeader.java index 9476651899d..d89bafbcf88 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryMessageRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryMessageRequestHeader.java @@ -18,7 +18,7 @@ /** * $Id: QueryMessageRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryMessageResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryMessageResponseHeader.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryMessageResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryMessageResponseHeader.java index a9f3f146893..b1927c5a9ba 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryMessageResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryMessageResponseHeader.java @@ -18,7 +18,7 @@ /** * $Id: QueryMessageResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMaxOffsetRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QuerySubscriptionByConsumerRequestHeader.java similarity index 77% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMaxOffsetRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QuerySubscriptionByConsumerRequestHeader.java index 871309de6ce..29d9234cd49 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMaxOffsetRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QuerySubscriptionByConsumerRequestHeader.java @@ -16,37 +16,37 @@ */ /** - * $Id: GetMaxOffsetRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ + * $Id: QueryMessageRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +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 GetMaxOffsetRequestHeader implements CommandCustomHeader { +public class QuerySubscriptionByConsumerRequestHeader implements CommandCustomHeader { @CFNotNull + private String group; private String topic; - @CFNotNull - private Integer queueId; @Override public void checkFields() throws RemotingCommandException { + } - public String getTopic() { - return topic; + public String getGroup() { + return group; } - public void setTopic(String topic) { - this.topic = topic; + public void setGroup(String group) { + this.group = group; } - public Integer getQueueId() { - return queueId; + public String getTopic() { + return topic; } - public void setQueueId(Integer queueId) { - this.queueId = queueId; + public void setTopic(String topic) { + this.topic = topic; } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryTopicConsumeByWhoRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryTopicConsumeByWhoRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryTopicConsumeByWhoRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryTopicConsumeByWhoRequestHeader.java index 3fba2e4960e..186cdefd90a 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/QueryTopicConsumeByWhoRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryTopicConsumeByWhoRequestHeader.java @@ -18,7 +18,7 @@ /** * $Id: QueryMessageRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryTopicsByConsumerRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryTopicsByConsumerRequestHeader.java new file mode 100644 index 00000000000..c172f612794 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/QueryTopicsByConsumerRequestHeader.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.remoting.CommandCustomHeader; +import org.apache.rocketmq.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; + +public class QueryTopicsByConsumerRequestHeader implements CommandCustomHeader { + @CFNotNull + private String group; + + @Override + public void checkFields() throws RemotingCommandException { + + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/RemoveBrokerRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/RemoveBrokerRequestHeader.java new file mode 100644 index 00000000000..a641340eeb9 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/RemoveBrokerRequestHeader.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 RemoveBrokerRequestHeader implements CommandCustomHeader { + @CFNotNull + private String brokerName; + @CFNotNull + 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ReplyMessageRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ReplyMessageRequestHeader.java similarity index 98% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/ReplyMessageRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ReplyMessageRequestHeader.java index 3bb09073f72..72e02ec9357 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ReplyMessageRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ReplyMessageRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResetMasterFlushOffsetHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResetMasterFlushOffsetHeader.java new file mode 100644 index 00000000000..ddee7224c7d --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResetMasterFlushOffsetHeader.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ResetOffsetRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResetOffsetRequestHeader.java similarity index 82% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/ResetOffsetRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResetOffsetRequestHeader.java index c3bfa218902..31723f8b829 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ResetOffsetRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResetOffsetRequestHeader.java @@ -15,19 +15,27 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +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 ResetOffsetRequestHeader implements CommandCustomHeader { + @CFNotNull private String topic; + @CFNotNull private String group; + + private int queueId = -1; + + private Long offset; + @CFNotNull private long timestamp; + @CFNotNull private boolean isForce; @@ -63,6 +71,22 @@ public void setForce(boolean isForce) { this.isForce = isForce; } + 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; + } + @Override public void checkFields() throws RemotingCommandException { diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ResumeCheckHalfMessageRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResumeCheckHalfMessageRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/ResumeCheckHalfMessageRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResumeCheckHalfMessageRequestHeader.java index 14dacd5e8dc..6265cdfd4be 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ResumeCheckHalfMessageRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResumeCheckHalfMessageRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNullable; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SearchOffsetRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SearchOffsetRequestHeader.java similarity index 76% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/SearchOffsetRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SearchOffsetRequestHeader.java index 5ea2e24bfc8..0c644d73930 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SearchOffsetRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SearchOffsetRequestHeader.java @@ -18,13 +18,14 @@ /** * $Id: SearchOffsetRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; -import org.apache.rocketmq.remoting.CommandCustomHeader; +import com.google.common.base.MoreObjects; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; +import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; -public class SearchOffsetRequestHeader implements CommandCustomHeader { +public class SearchOffsetRequestHeader extends TopicQueueRequestHeader { @CFNotNull private String topic; @CFNotNull @@ -37,18 +38,22 @@ 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; } @@ -61,4 +66,12 @@ public void setTimestamp(Long timestamp) { this.timestamp = timestamp; } + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("topic", topic) + .add("queueId", queueId) + .add("timestamp", timestamp) + .toString(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SearchOffsetResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SearchOffsetResponseHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/SearchOffsetResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SearchOffsetResponseHeader.java index f88ac6852f4..fe4006219eb 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SearchOffsetResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SearchOffsetResponseHeader.java @@ -18,7 +18,7 @@ /** * $Id: SearchOffsetResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageRequestHeader.java similarity index 64% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageRequestHeader.java index 2df31e6bb2a..17ce5126313 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageRequestHeader.java @@ -18,14 +18,17 @@ /** * $Id: SendMessageRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; -import org.apache.rocketmq.remoting.CommandCustomHeader; +import com.google.common.base.MoreObjects; 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; -public class SendMessageRequestHeader implements CommandCustomHeader { +public class SendMessageRequestHeader extends TopicQueueRequestHeader { @CFNotNull private String producerGroup; @CFNotNull @@ -64,10 +67,12 @@ public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } + @Override public String getTopic() { return topic; } + @Override public void setTopic(String topic) { this.topic = topic; } @@ -88,10 +93,12 @@ public void setDefaultTopicQueueNums(Integer defaultTopicQueueNums) { this.defaultTopicQueueNums = defaultTopicQueueNums; } + @Override public Integer getQueueId() { return queueId; } + @Override public void setQueueId(Integer queueId) { this.queueId = queueId; } @@ -159,4 +166,46 @@ public boolean isBatch() { 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 = + (SendMessageRequestHeaderV2) request + .decodeCommandCustomHeader(SendMessageRequestHeaderV2.class); + case RequestCode.SEND_MESSAGE: + if (null == requestHeaderV2) { + requestHeader = + (SendMessageRequestHeader) 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(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeaderV2.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageRequestHeaderV2.java similarity index 60% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeaderV2.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageRequestHeaderV2.java index 4e0098b5f0e..0fd0889bbdb 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeaderV2.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageRequestHeaderV2.java @@ -15,17 +15,21 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +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.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; /** * Use short variable name to speed up FastJson deserialization process. */ -public class SendMessageRequestHeaderV2 implements CommandCustomHeader { +public class SendMessageRequestHeaderV2 implements CommandCustomHeader, FastCodesHeader { @CFNotNull private String a; // producerGroup; @CFNotNull @@ -53,6 +57,8 @@ public class SendMessageRequestHeaderV2 implements CommandCustomHeader { @CFNullable private boolean m; //batch + @CFNullable + private String n; // brokerName public static SendMessageRequestHeader createSendMessageRequestHeaderV1(final SendMessageRequestHeaderV2 v2) { SendMessageRequestHeader v1 = new SendMessageRequestHeader(); @@ -69,6 +75,7 @@ public static SendMessageRequestHeader createSendMessageRequestHeaderV1(final Se v1.setUnitMode(v2.k); v1.setMaxReconsumeTimes(v2.l); v1.setBatch(v2.m); + v1.setBname(v2.n); return v1; } @@ -87,6 +94,7 @@ public static SendMessageRequestHeaderV2 createSendMessageRequestHeaderV2(final v2.k = v1.isUnitMode(); v2.l = v1.getMaxReconsumeTimes(); v2.m = v1.isBatch(); + v2.n = v1.getBname(); return v2; } @@ -94,6 +102,98 @@ public static SendMessageRequestHeaderV2 createSendMessageRequestHeaderV2(final 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; } @@ -197,4 +297,24 @@ public boolean isM() { public void setM(boolean m) { this.m = m; } -} \ No newline at end of file + + @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(); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageResponseHeader.java new file mode 100644 index 00000000000..fe1e8533e54 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/SendMessageResponseHeader.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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; + + @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); + } + + @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; + } + } + + 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/StatisticsMessagesRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/StatisticsMessagesRequestHeader.java new file mode 100644 index 00000000000..16b7ecb5ced --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/StatisticsMessagesRequestHeader.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 StatisticsMessagesRequestHeader implements CommandCustomHeader { + @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 int getQueueId() { + if (queueId < 0) { + return -1; + } + return queueId; + } + + public void setQueueId(int 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/UnregisterClientRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UnregisterClientRequestHeader.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/UnregisterClientRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UnregisterClientRequestHeader.java index bb0a4629170..371a5479833 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/UnregisterClientRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UnregisterClientRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/UnregisterClientResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UnregisterClientResponseHeader.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/UnregisterClientResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UnregisterClientResponseHeader.java index 38fb87a6e23..f7347c5d477 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/UnregisterClientResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UnregisterClientResponseHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateConsumerOffsetRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateConsumerOffsetRequestHeader.java similarity index 76% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateConsumerOffsetRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateConsumerOffsetRequestHeader.java index 3f44db645c5..f131c36f057 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateConsumerOffsetRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateConsumerOffsetRequestHeader.java @@ -18,13 +18,14 @@ /** * $Id: UpdateConsumerOffsetRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; -import org.apache.rocketmq.remoting.CommandCustomHeader; +import com.google.common.base.MoreObjects; import org.apache.rocketmq.remoting.annotation.CFNotNull; import org.apache.rocketmq.remoting.exception.RemotingCommandException; +import org.apache.rocketmq.remoting.rpc.TopicQueueRequestHeader; -public class UpdateConsumerOffsetRequestHeader implements CommandCustomHeader { +public class UpdateConsumerOffsetRequestHeader extends TopicQueueRequestHeader { @CFNotNull private String consumerGroup; @CFNotNull @@ -46,18 +47,22 @@ 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; } @@ -69,4 +74,14 @@ public Long getCommitOffset() { 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(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateConsumerOffsetResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateConsumerOffsetResponseHeader.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateConsumerOffsetResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateConsumerOffsetResponseHeader.java index 3cd9ebfcc2e..13b5b8752b1 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateConsumerOffsetResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateConsumerOffsetResponseHeader.java @@ -18,7 +18,7 @@ /** * $Id: UpdateConsumerOffsetResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateGlobalWhiteAddrsConfigRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateGlobalWhiteAddrsConfigRequestHeader.java similarity index 78% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateGlobalWhiteAddrsConfigRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateGlobalWhiteAddrsConfigRequestHeader.java index 2d42c750cf2..59e93d32630 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateGlobalWhiteAddrsConfigRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateGlobalWhiteAddrsConfigRequestHeader.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; @@ -24,8 +24,11 @@ public class UpdateGlobalWhiteAddrsConfigRequestHeader implements CommandCustomH @CFNotNull private String globalWhiteAddrs; + @CFNotNull + private String aclFileFullPath; - @Override public void checkFields() throws RemotingCommandException { + @Override + public void checkFields() throws RemotingCommandException { } @@ -36,4 +39,12 @@ public String getGlobalWhiteAddrs() { public void setGlobalWhiteAddrs(String globalWhiteAddrs) { this.globalWhiteAddrs = globalWhiteAddrs; } + + public String getAclFileFullPath() { + return aclFileFullPath; + } + + public void setAclFileFullPath(String aclFileFullPath) { + this.aclFileFullPath = aclFileFullPath; + } } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateGroupForbiddenRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateGroupForbiddenRequestHeader.java new file mode 100644 index 00000000000..de2f9d1fbdf --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/UpdateGroupForbiddenRequestHeader.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.remoting.CommandCustomHeader; +import org.apache.rocketmq.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; + +public class UpdateGroupForbiddenRequestHeader implements CommandCustomHeader { + @CFNotNull + private String group; + @CFNotNull + 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; + } + +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ViewBrokerStatsDataRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ViewBrokerStatsDataRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/ViewBrokerStatsDataRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ViewBrokerStatsDataRequestHeader.java index 646cf3a1c91..b879bfbac24 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ViewBrokerStatsDataRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ViewBrokerStatsDataRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ViewMessageRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ViewMessageRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/ViewMessageRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ViewMessageRequestHeader.java index c0b937363bc..79421fee4c0 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ViewMessageRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ViewMessageRequestHeader.java @@ -18,7 +18,7 @@ /** * $Id: ViewMessageRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ViewMessageResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ViewMessageResponseHeader.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/ViewMessageResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ViewMessageResponseHeader.java index e7153dd189b..94484e04b27 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ViewMessageResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ViewMessageResponseHeader.java @@ -18,7 +18,7 @@ /** * $Id: ViewMessageResponseHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header; +package org.apache.rocketmq.remoting.protocol.header; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.exception.RemotingCommandException; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/AlterSyncStateSetRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/AlterSyncStateSetRequestHeader.java new file mode 100644 index 00000000000..5161d74dcfb --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/AlterSyncStateSetRequestHeader.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 AlterSyncStateSetRequestHeader implements CommandCustomHeader { + private String brokerName; + private Long masterBrokerId; + private Integer masterEpoch; + + public AlterSyncStateSetRequestHeader() { + } + + public AlterSyncStateSetRequestHeader(String brokerName, Long masterBrokerId, Integer masterEpoch) { + this.brokerName = brokerName; + this.masterBrokerId = masterBrokerId; + this.masterEpoch = masterEpoch; + } + + 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 { + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/AlterSyncStateSetResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/AlterSyncStateSetResponseHeader.java new file mode 100644 index 00000000000..012197c73b6 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/AlterSyncStateSetResponseHeader.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 { + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/ElectMasterRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/ElectMasterRequestHeader.java new file mode 100644 index 00000000000..8071fc5f5d9 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/ElectMasterRequestHeader.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.annotation.CFNotNull; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; + +public class ElectMasterRequestHeader implements CommandCustomHeader { + + @CFNotNull + 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; + + @CFNotNull + private Boolean designateElect = false; + + 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; + } + + @Override + public String toString() { + return "ElectMasterRequestHeader{" + + "clusterName='" + clusterName + '\'' + + ", brokerName='" + brokerName + '\'' + + ", brokerId=" + brokerId + + ", designateElect=" + designateElect + + '}'; + } + + @Override + public void checkFields() throws RemotingCommandException { + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/ElectMasterResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/ElectMasterResponseHeader.java new file mode 100644 index 00000000000..aaf3b10b829 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/ElectMasterResponseHeader.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 { + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/GetMetaDataResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/GetMetaDataResponseHeader.java new file mode 100644 index 00000000000..9ae6c7ca08d --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/GetMetaDataResponseHeader.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 { + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/GetReplicaInfoRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/GetReplicaInfoRequestHeader.java new file mode 100644 index 00000000000..efc7afc8498 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/GetReplicaInfoRequestHeader.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 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 { + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/GetReplicaInfoResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/GetReplicaInfoResponseHeader.java new file mode 100644 index 00000000000..f7aa49e6970 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/GetReplicaInfoResponseHeader.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 { + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/admin/CleanControllerBrokerDataRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/admin/CleanControllerBrokerDataRequestHeader.java new file mode 100644 index 00000000000..8b94494cf0c --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/admin/CleanControllerBrokerDataRequestHeader.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.remoting.CommandCustomHeader; +import org.apache.rocketmq.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.annotation.CFNullable; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; + +public class CleanControllerBrokerDataRequestHeader implements CommandCustomHeader { + + @CFNullable + private String clusterName; + + @CFNotNull + private String brokerName; + + @CFNullable + private String brokerControllerIdsToClean; + + private boolean isCleanLivingBroker = false; + + 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 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 + + '}'; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/ApplyBrokerIdRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/ApplyBrokerIdRequestHeader.java new file mode 100644 index 00000000000..c577d6a736a --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/ApplyBrokerIdRequestHeader.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 ApplyBrokerIdRequestHeader implements CommandCustomHeader { + + 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/ApplyBrokerIdResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/ApplyBrokerIdResponseHeader.java new file mode 100644 index 00000000000..a7f100f778d --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/ApplyBrokerIdResponseHeader.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/GetNextBrokerIdRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/GetNextBrokerIdRequestHeader.java new file mode 100644 index 00000000000..aeb222955e0 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/GetNextBrokerIdRequestHeader.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 GetNextBrokerIdRequestHeader implements CommandCustomHeader { + + 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/GetNextBrokerIdResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/GetNextBrokerIdResponseHeader.java new file mode 100644 index 00000000000..7d62722d4df --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/GetNextBrokerIdResponseHeader.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/RegisterBrokerToControllerRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/RegisterBrokerToControllerRequestHeader.java new file mode 100644 index 00000000000..7e995b4794c --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/RegisterBrokerToControllerRequestHeader.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 RegisterBrokerToControllerRequestHeader implements CommandCustomHeader { + + private String clusterName; + + private String brokerName; + + private Long brokerId; + + private String brokerAddress; + + public RegisterBrokerToControllerRequestHeader() { + } + + public RegisterBrokerToControllerRequestHeader(String clusterName, String brokerName, Long brokerId, String brokerAddress) { + this.clusterName = clusterName; + this.brokerName = brokerName; + this.brokerId = brokerId; + this.brokerAddress = brokerAddress; + } + + @Override + public void checkFields() throws RemotingCommandException { + + } + + 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/RegisterBrokerToControllerResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/RegisterBrokerToControllerResponseHeader.java new file mode 100644 index 00000000000..66bf0e44151 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/controller/register/RegisterBrokerToControllerResponseHeader.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/AddWritePermOfBrokerRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/AddWritePermOfBrokerRequestHeader.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/AddWritePermOfBrokerRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/AddWritePermOfBrokerRequestHeader.java index 17fd3f5ea7e..6c50916282e 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/AddWritePermOfBrokerRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/AddWritePermOfBrokerRequestHeader.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/AddWritePermOfBrokerResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/AddWritePermOfBrokerResponseHeader.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/AddWritePermOfBrokerResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/AddWritePermOfBrokerResponseHeader.java index d217206a940..50bf6a9ed11 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/AddWritePermOfBrokerResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/AddWritePermOfBrokerResponseHeader.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/BrokerHeartbeatRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/BrokerHeartbeatRequestHeader.java new file mode 100644 index 00000000000..eb7332fdf40 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/BrokerHeartbeatRequestHeader.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.annotation.CFNullable; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; + +public class BrokerHeartbeatRequestHeader implements CommandCustomHeader { + @CFNotNull + 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; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/DeleteKVConfigRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/DeleteKVConfigRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/DeleteKVConfigRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/DeleteKVConfigRequestHeader.java index 3245187cc03..7fcbe97e8db 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/DeleteKVConfigRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/DeleteKVConfigRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/DeleteTopicFromNamesrvRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/DeleteTopicFromNamesrvRequestHeader.java similarity index 83% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/DeleteTopicFromNamesrvRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/DeleteTopicFromNamesrvRequestHeader.java index ed8e7a3970a..ec0101e7f93 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/DeleteTopicFromNamesrvRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/DeleteTopicFromNamesrvRequestHeader.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; @@ -24,6 +24,8 @@ public class DeleteTopicFromNamesrvRequestHeader implements CommandCustomHeader @CFNotNull private String topic; + private String clusterName; + @Override public void checkFields() throws RemotingCommandException { } @@ -35,4 +37,12 @@ public String getTopic() { public void setTopic(String topic) { this.topic = topic; } + + public String getClusterName() { + return clusterName; + } + + public void setClusterName(String clusterName) { + this.clusterName = clusterName; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/GetKVConfigRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetKVConfigRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/GetKVConfigRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetKVConfigRequestHeader.java index 14a0340a06d..3a069afeb8a 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/GetKVConfigRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetKVConfigRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/GetKVConfigResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetKVConfigResponseHeader.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/GetKVConfigResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetKVConfigResponseHeader.java index ef4859e521d..e5b1113870a 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/GetKVConfigResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetKVConfigResponseHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNullable; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/GetKVListByNamespaceRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetKVListByNamespaceRequestHeader.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/GetKVListByNamespaceRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetKVListByNamespaceRequestHeader.java index 6c60b2f96ee..9876161e173 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/GetKVListByNamespaceRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetKVListByNamespaceRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/GetRouteInfoRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetRouteInfoRequestHeader.java similarity index 77% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/GetRouteInfoRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetRouteInfoRequestHeader.java index a2806e62897..0993f81fde0 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/GetRouteInfoRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/GetRouteInfoRequestHeader.java @@ -18,16 +18,21 @@ /** * $Id: GetRouteInfoRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header.namesrv; +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.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class GetRouteInfoRequestHeader implements CommandCustomHeader { + @CFNotNull private String topic; + @CFNullable + private Boolean acceptStandardJsonOnly; + @Override public void checkFields() throws RemotingCommandException { } @@ -39,4 +44,12 @@ public String getTopic() { public void setTopic(String topic) { this.topic = topic; } + + public Boolean getAcceptStandardJsonOnly() { + return acceptStandardJsonOnly; + } + + public void setAcceptStandardJsonOnly(Boolean acceptStandardJsonOnly) { + this.acceptStandardJsonOnly = acceptStandardJsonOnly; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/PutKVConfigRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/PutKVConfigRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/PutKVConfigRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/PutKVConfigRequestHeader.java index 22e17e06f37..60f16cbea35 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/PutKVConfigRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/PutKVConfigRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/QueryDataVersionRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/QueryDataVersionRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/QueryDataVersionRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/QueryDataVersionRequestHeader.java index ac6a617db2f..2a1e95b2ce7 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/QueryDataVersionRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/QueryDataVersionRequestHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/QueryDataVersionResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/QueryDataVersionResponseHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/QueryDataVersionResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/QueryDataVersionResponseHeader.java index 90741e5f5b3..94e83ba8531 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/QueryDataVersionResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/QueryDataVersionResponseHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/RegisterBrokerRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterBrokerRequestHeader.java similarity index 79% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/RegisterBrokerRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterBrokerRequestHeader.java index 19175b04b3e..f97d40daa9d 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/RegisterBrokerRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterBrokerRequestHeader.java @@ -18,10 +18,11 @@ /** * $Id: RegisterBrokerRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header.namesrv; +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.annotation.CFNullable; import org.apache.rocketmq.remoting.exception.RemotingCommandException; public class RegisterBrokerRequestHeader implements CommandCustomHeader { @@ -35,11 +36,16 @@ public class RegisterBrokerRequestHeader implements CommandCustomHeader { 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 { } @@ -83,6 +89,14 @@ 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; } @@ -98,4 +112,12 @@ public Integer getBodyCrc32() { public void setBodyCrc32(Integer bodyCrc32) { this.bodyCrc32 = bodyCrc32; } + + public Boolean getEnableActingMaster() { + return enableActingMaster; + } + + public void setEnableActingMaster(Boolean enableActingMaster) { + this.enableActingMaster = enableActingMaster; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/RegisterBrokerResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterBrokerResponseHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/RegisterBrokerResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterBrokerResponseHeader.java index da4b56c8823..0e35187f3c6 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/RegisterBrokerResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterBrokerResponseHeader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNullable; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/RegisterOrderTopicRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterOrderTopicRequestHeader.java similarity index 96% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/RegisterOrderTopicRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterOrderTopicRequestHeader.java index 8307e20b712..39bb83350dd 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/RegisterOrderTopicRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterOrderTopicRequestHeader.java @@ -18,7 +18,7 @@ /** * $Id: RegisterOrderTopicRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterTopicRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterTopicRequestHeader.java new file mode 100644 index 00000000000..ce36ab0f212 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterTopicRequestHeader.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 RegisterTopicRequestHeader implements CommandCustomHeader { + @CFNotNull + private String topic; + + @Override + public void checkFields() throws RemotingCommandException { + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/UnRegisterBrokerRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/UnRegisterBrokerRequestHeader.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/UnRegisterBrokerRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/UnRegisterBrokerRequestHeader.java index 6787ecfa755..e0609791f77 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/UnRegisterBrokerRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/UnRegisterBrokerRequestHeader.java @@ -18,7 +18,7 @@ /** * $Id: UnRegisterBrokerRequestHeader.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/WipeWritePermOfBrokerRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/WipeWritePermOfBrokerRequestHeader.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/WipeWritePermOfBrokerRequestHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/WipeWritePermOfBrokerRequestHeader.java index 2be8fe6cb6a..edd87d1111d 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/WipeWritePermOfBrokerRequestHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/WipeWritePermOfBrokerRequestHeader.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/WipeWritePermOfBrokerResponseHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/WipeWritePermOfBrokerResponseHeader.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/WipeWritePermOfBrokerResponseHeader.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/WipeWritePermOfBrokerResponseHeader.java index 0fc29523dc5..bd09e4b82c7 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/namesrv/WipeWritePermOfBrokerResponseHeader.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/WipeWritePermOfBrokerResponseHeader.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.protocol.header.namesrv; +package org.apache.rocketmq.remoting.protocol.header.namesrv; import org.apache.rocketmq.remoting.CommandCustomHeader; import org.apache.rocketmq.remoting.annotation.CFNotNull; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/ConsumeType.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/ConsumeType.java similarity index 90% rename from common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/ConsumeType.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/ConsumeType.java index f2554dea7a2..fbcca5d5eff 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/ConsumeType.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/ConsumeType.java @@ -18,13 +18,15 @@ /** * $Id: ConsumeType.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.heartbeat; +package org.apache.rocketmq.remoting.protocol.heartbeat; public enum ConsumeType { CONSUME_ACTIVELY("PULL"), - CONSUME_PASSIVELY("PUSH"); + CONSUME_PASSIVELY("PUSH"), + + CONSUME_POP("POP"); private String typeCN; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/ConsumerData.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/ConsumerData.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/ConsumerData.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/ConsumerData.java index d4605d069aa..fe1e8dfa8ff 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/ConsumerData.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/ConsumerData.java @@ -18,7 +18,7 @@ /** * $Id: ConsumerData.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.heartbeat; +package org.apache.rocketmq.remoting.protocol.heartbeat; import java.util.HashSet; import java.util.Set; @@ -29,7 +29,7 @@ public class ConsumerData { private ConsumeType consumeType; private MessageModel messageModel; private ConsumeFromWhere consumeFromWhere; - private Set subscriptionDataSet = new HashSet(); + private Set subscriptionDataSet = new HashSet<>(); private boolean unitMode; public String getGroupName() { diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/HeartbeatData.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/HeartbeatData.java similarity index 59% rename from common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/HeartbeatData.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/HeartbeatData.java index 47ae542a33f..f7b4b9faeff 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/HeartbeatData.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/HeartbeatData.java @@ -18,16 +18,19 @@ /** * $Id: HeartbeatData.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.heartbeat; +package org.apache.rocketmq.remoting.protocol.heartbeat; import java.util.HashSet; import java.util.Set; +import com.alibaba.fastjson.JSON; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; public class HeartbeatData extends RemotingSerializable { private String clientID; - private Set producerDataSet = new HashSet(); - private Set consumerDataSet = new HashSet(); + private Set producerDataSet = new HashSet<>(); + private Set consumerDataSet = new HashSet<>(); + private int heartbeatFingerprint = 0; + private boolean isWithoutSub = false; public String getClientID() { return clientID; @@ -53,9 +56,38 @@ 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), 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).hashCode(); + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/MessageModel.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/MessageModel.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/MessageModel.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/MessageModel.java index defe676f6ea..11f2e6c9ec4 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/MessageModel.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/MessageModel.java @@ -18,7 +18,7 @@ /** * $Id: MessageModel.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.heartbeat; +package org.apache.rocketmq.remoting.protocol.heartbeat; /** * Message model diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/ProducerData.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/ProducerData.java similarity index 95% rename from common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/ProducerData.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/ProducerData.java index 279996a7948..ebf5fc44547 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/ProducerData.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/ProducerData.java @@ -18,7 +18,7 @@ /** * $Id: ProducerData.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.heartbeat; +package org.apache.rocketmq.remoting.protocol.heartbeat; public class ProducerData { private String groupName; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/SubscriptionData.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/SubscriptionData.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/SubscriptionData.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/SubscriptionData.java index 83e254f22ed..59088fc42e5 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/heartbeat/SubscriptionData.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/heartbeat/SubscriptionData.java @@ -18,21 +18,20 @@ /** * $Id: SubscriptionData.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.heartbeat; +package org.apache.rocketmq.remoting.protocol.heartbeat; import com.alibaba.fastjson.annotation.JSONField; -import org.apache.rocketmq.common.filter.ExpressionType; - import java.util.HashSet; import java.util.Set; +import org.apache.rocketmq.common.filter.ExpressionType; 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 Set tagsSet = new HashSet<>(); + private Set codeSet = new HashSet<>(); private long subVersion = System.currentTimeMillis(); private String expressionType = ExpressionType.TAG; diff --git a/common/src/main/java/org/apache/rocketmq/common/namesrv/RegisterBrokerResult.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/namesrv/RegisterBrokerResult.java similarity index 92% rename from common/src/main/java/org/apache/rocketmq/common/namesrv/RegisterBrokerResult.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/namesrv/RegisterBrokerResult.java index 5d803332db0..edbed3e843f 100644 --- a/common/src/main/java/org/apache/rocketmq/common/namesrv/RegisterBrokerResult.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/namesrv/RegisterBrokerResult.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package org.apache.rocketmq.common.namesrv; +package org.apache.rocketmq.remoting.protocol.namesrv; -import org.apache.rocketmq.common.protocol.body.KVTable; +import org.apache.rocketmq.remoting.protocol.body.KVTable; public class RegisterBrokerResult { private String haServerAddr; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/route/BrokerData.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/BrokerData.java similarity index 50% rename from common/src/main/java/org/apache/rocketmq/common/protocol/route/BrokerData.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/BrokerData.java index 36599fbc874..de911d17c26 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/route/BrokerData.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/BrokerData.java @@ -15,46 +15,88 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.route; +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; - private HashMap brokerAddrs; + /** + * 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. + * 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 addr = this.brokerAddrs.get(MixAll.MASTER_ID); + String masterAddress = this.brokerAddrs.get(MixAll.MASTER_ID); - if (addr == null) { - List addrs = new ArrayList(brokerAddrs.values()); + if (masterAddress == null) { + List addrs = new ArrayList<>(brokerAddrs.values()); return addrs.get(random.nextInt(addrs.size())); } - return addr; + return masterAddress; } public HashMap getBrokerAddrs() { @@ -73,6 +115,22 @@ 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; @@ -84,29 +142,29 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + 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; - if (brokerName == null) { - if (other.brokerName != null) + if (other.brokerAddrs != null) { return false; - } else if (!brokerName.equals(other.brokerName)) + } + } else if (!brokerAddrs.equals(other.brokerAddrs)) { return false; - return true; + } + return StringUtils.equals(brokerName, other.brokerName); } @Override public String toString() { - return "BrokerData [brokerName=" + brokerName + ", brokerAddrs=" + brokerAddrs + "]"; + return "BrokerData [brokerName=" + brokerName + ", brokerAddrs=" + brokerAddrs + ", enableActingMaster=" + enableActingMaster + "]"; } @Override diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/MessageQueueRouteState.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/MessageQueueRouteState.java new file mode 100644 index 00000000000..0e43a6f3812 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/MessageQueueRouteState.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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, + ; +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/route/QueueData.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/QueueData.java similarity index 86% rename from common/src/main/java/org/apache/rocketmq/common/protocol/route/QueueData.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/QueueData.java index 2dbb2902c9b..3678e400758 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/route/QueueData.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/QueueData.java @@ -15,10 +15,10 @@ * limitations under the License. */ -/** - * $Id: QueueData.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ +/* + $Id: QueueData.java 1835 2013-05-16 02:00:50Z vintagewang@apache.org $ */ -package org.apache.rocketmq.common.protocol.route; +package org.apache.rocketmq.remoting.protocol.route; public class QueueData implements Comparable { private String brokerName; @@ -27,6 +27,19 @@ public class QueueData implements Comparable { 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; } @@ -91,9 +104,7 @@ public boolean equals(Object obj) { return false; if (writeQueueNums != other.writeQueueNums) return false; - if (topicSysFlag != other.topicSysFlag) - return false; - return true; + return topicSysFlag == other.topicSysFlag; } @Override diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/TopicRouteData.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/TopicRouteData.java new file mode 100644 index 00000000000..2ef9923eb7c --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/route/TopicRouteData.java @@ -0,0 +1,226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + "]"; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/LogicQueueMappingItem.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/LogicQueueMappingItem.java new file mode 100644 index 00000000000..0c5bbb6a974 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/LogicQueueMappingItem.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 + + '}'; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicConfigAndQueueMapping.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicConfigAndQueueMapping.java new file mode 100644 index 00000000000..c937fec2326 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicConfigAndQueueMapping.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.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(); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingContext.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingContext.java new file mode 100644 index 00000000000..81718c8bc11 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingContext.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingDetail.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingDetail.java new file mode 100644 index 00000000000..5c6e4d29847 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingDetail.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 + + '}'; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingInfo.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingInfo.java new file mode 100644 index 00000000000..4325a429d3a --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingInfo.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 + + '}'; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingOne.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingOne.java new file mode 100644 index 00000000000..8cbbd59ee6c --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingOne.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingUtils.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingUtils.java new file mode 100644 index 00000000000..45cbed75727 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingUtils.java @@ -0,0 +1,684 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 dose 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 dose 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 dose 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 dose 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 dose not increase monotonically"); + } + if (item.computeMaxStaticQueueOffset() >= lastOffset) { + throw new RuntimeException("The max logic offset dose 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 dose not begin from 0"); + } + TopicConfig topicConfig = brokerConfigMap.get(item.getBname()); + if (topicConfig == null) { + throw new RuntimeException("The broker of item dose 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 dose 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 + " dose 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()); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicRemappingDetailWrapper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicRemappingDetailWrapper.java new file mode 100644 index 00000000000..75522bf3d7e --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicRemappingDetailWrapper.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/CustomizedRetryPolicy.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/CustomizedRetryPolicy.java new file mode 100644 index 00000000000..a8cdc748872 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/CustomizedRetryPolicy.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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]; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/ExponentialRetryPolicy.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/ExponentialRetryPolicy.java new file mode 100644 index 00000000000..937c99d1c8c --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/ExponentialRetryPolicy.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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)); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/GroupForbidden.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/GroupForbidden.java new file mode 100644 index 00000000000..5d509902c5e --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/GroupForbidden.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 + "]"; + } + +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/GroupRetryPolicy.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/GroupRetryPolicy.java new file mode 100644 index 00000000000..14d5e537697 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/GroupRetryPolicy.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.fastjson.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(); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/GroupRetryPolicyType.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/GroupRetryPolicyType.java new file mode 100644 index 00000000000..f68b127f1d1 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/GroupRetryPolicyType.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/RetryPolicy.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/RetryPolicy.java new file mode 100644 index 00000000000..2a77fa88a56 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/RetryPolicy.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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); +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/SimpleSubscriptionData.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/SimpleSubscriptionData.java new file mode 100644 index 00000000000..ec2b51e0b96 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/SimpleSubscriptionData.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 version == that.version && Objects.equals(topic, that.topic); + } + + @Override public int hashCode() { + return Objects.hash(topic, version); + } + + @Override public String toString() { + return MoreObjects.toStringHelper(this) + .add("topic", topic) + .add("expressionType", expressionType) + .add("expression", expression) + .add("version", version) + .toString(); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/subscription/SubscriptionGroupConfig.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/SubscriptionGroupConfig.java similarity index 52% rename from common/src/main/java/org/apache/rocketmq/common/subscription/SubscriptionGroupConfig.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/SubscriptionGroupConfig.java index 8f4703f6c80..5522059aaf5 100644 --- a/common/src/main/java/org/apache/rocketmq/common/subscription/SubscriptionGroupConfig.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/subscription/SubscriptionGroupConfig.java @@ -15,8 +15,13 @@ * limitations under the License. */ -package org.apache.rocketmq.common.subscription; +package org.apache.rocketmq.remoting.protocol.subscription; +import com.google.common.base.MoreObjects; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.rocketmq.common.MixAll; public class SubscriptionGroupConfig { @@ -25,12 +30,13 @@ public class SubscriptionGroupConfig { 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; @@ -38,6 +44,15 @@ public class SubscriptionGroupConfig { 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; } @@ -70,6 +85,14 @@ 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; } @@ -86,6 +109,14 @@ 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; } @@ -110,6 +141,38 @@ public void setNotifyConsumerIdsChangedEnable(final boolean notifyConsumerIdsCha 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; + } + @Override public int hashCode() { final int prime = 31; @@ -124,6 +187,10 @@ public int hashCode() { result = prime * result + retryQueueNums; result = prime * result + (int) (whichBrokerWhenConsumeSlowly ^ (whichBrokerWhenConsumeSlowly >>> 32)); + result = prime * result + groupSysFlag; + result = prime * result + consumeTimeoutMinute; + result = prime * result + subscriptionDataSet.hashCode(); + result = prime * result + attributes.hashCode(); return result; } @@ -136,37 +203,40 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; SubscriptionGroupConfig other = (SubscriptionGroupConfig) obj; - if (brokerId != other.brokerId) - return false; - if (consumeBroadcastEnable != other.consumeBroadcastEnable) - return false; - if (consumeEnable != other.consumeEnable) - return false; - if (consumeFromMinEnable != other.consumeFromMinEnable) - return false; - if (groupName == null) { - if (other.groupName != null) - return false; - } else if (!groupName.equals(other.groupName)) - return false; - if (retryMaxTimes != other.retryMaxTimes) - return false; - if (retryQueueNums != other.retryQueueNums) - return false; - if (whichBrokerWhenConsumeSlowly != other.whichBrokerWhenConsumeSlowly) - return false; - if (notifyConsumerIdsChangedEnable != other.notifyConsumerIdsChangedEnable) - return false; - return true; + return new EqualsBuilder() + .append(groupName, other.groupName) + .append(consumeEnable, other.consumeEnable) + .append(consumeFromMinEnable, other.consumeFromMinEnable) + .append(consumeBroadcastEnable, other.consumeBroadcastEnable) + .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 "SubscriptionGroupConfig [groupName=" + groupName + ", consumeEnable=" + consumeEnable - + ", consumeFromMinEnable=" + consumeFromMinEnable + ", consumeBroadcastEnable=" - + consumeBroadcastEnable + ", retryQueueNums=" + retryQueueNums + ", retryMaxTimes=" - + retryMaxTimes + ", brokerId=" + brokerId + ", whichBrokerWhenConsumeSlowly=" - + whichBrokerWhenConsumeSlowly + ", notifyConsumerIdsChangedEnable=" - + notifyConsumerIdsChangedEnable + "]"; + 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(); } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/topic/OffsetMovedEvent.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/topic/OffsetMovedEvent.java similarity index 97% rename from common/src/main/java/org/apache/rocketmq/common/protocol/topic/OffsetMovedEvent.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/protocol/topic/OffsetMovedEvent.java index 1c03c0713d0..ee877161fe5 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/topic/OffsetMovedEvent.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/topic/OffsetMovedEvent.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.topic; +package org.apache.rocketmq.remoting.protocol.topic; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/proxy/SocksProxyConfig.java b/remoting/src/main/java/org/apache/rocketmq/remoting/proxy/SocksProxyConfig.java new file mode 100644 index 00000000000..f59b5dd873d --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/proxy/SocksProxyConfig.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/ClientMetadata.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/ClientMetadata.java new file mode 100644 index 00000000000..d4962e00a58 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/ClientMetadata.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + } + +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RequestBuilder.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RequestBuilder.java new file mode 100644 index 00000000000..79167ec2668 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RequestBuilder.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.setOway(oneway); + requestHeader.setBname(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.setOway(oneway); + requestHeader.setBname(destBrokerName); + requestHeader.setTopic(topic); + requestHeader.setQueueId(queueId); + requestHeader.setLo(logic); + return requestHeader; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClient.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClient.java new file mode 100644 index 00000000000..f1df83bc7d8 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClient.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientHook.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientHook.java new file mode 100644 index 00000000000..56751483481 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientHook.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientImpl.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientImpl.java new file mode 100644 index 00000000000..133e0ed314e --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientImpl.java @@ -0,0 +1,340 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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().setBname(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) { + RemotingCommand responseCommand = responseFuture.getResponseCommand(); + if (responseCommand == null) { + processFailedResponse(addr, requestCommand, responseFuture, rpcResponsePromise); + return; + } + try { + switch (responseCommand.getCode()) { + case ResponseCode.SUCCESS: + case ResponseCode.PULL_NOT_FOUND: + case ResponseCode.PULL_RETRY_IMMEDIATELY: + case ResponseCode.PULL_OFFSET_MOVED: + PullMessageResponseHeader responseHeader = + (PullMessageResponseHeader) responseCommand.decodeCommandCustomHeader(PullMessageResponseHeader.class); + rpcResponsePromise.setSuccess(new RpcResponse(responseCommand.getCode(), responseHeader, responseCommand.getBody())); + default: + RpcResponse rpcResponse = new RpcResponse(new RpcException(responseCommand.getCode(), "unexpected remote response code")); + rpcResponsePromise.setSuccess(rpcResponse); + + } + } catch (Exception e) { + String errorMessage = "process failed. addr: " + addr + ", timeoutMillis: " + responseFuture.getTimeoutMillis() + ". Request: " + requestCommand; + RpcResponse rpcResponse = new RpcResponse(new RpcException(ResponseCode.RPC_UNKNOWN, errorMessage, e)); + 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; + } + +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientUtils.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientUtils.java new file mode 100644 index 00000000000..78a33b72c59 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientUtils.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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()); + } + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcException.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcException.java new file mode 100644 index 00000000000..dda918b33ec --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcException.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/common/Pair.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcRequest.java similarity index 63% rename from remoting/src/main/java/org/apache/rocketmq/remoting/common/Pair.java rename to remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcRequest.java index 01e761fcfae..3bf06c1f985 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/common/Pair.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcRequest.java @@ -14,30 +14,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.remoting.common; +package org.apache.rocketmq.remoting.rpc; -public class Pair { - private T1 object1; - private T2 object2; +public class RpcRequest { + int code; + private RpcRequestHeader header; + private Object body; - public Pair(T1 object1, T2 object2) { - this.object1 = object1; - this.object2 = object2; + public RpcRequest(int code, RpcRequestHeader header, Object body) { + this.code = code; + this.header = header; + this.body = body; } - public T1 getObject1() { - return object1; + public RpcRequestHeader getHeader() { + return header; } - public void setObject1(T1 object1) { - this.object1 = object1; + public Object getBody() { + return body; } - public T2 getObject2() { - return object2; - } - - public void setObject2(T2 object2) { - this.object2 = object2; + public int getCode() { + return code; } } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcRequestHeader.java new file mode 100644 index 00000000000..ef7e53b4e6b --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcRequestHeader.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 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; + + public String getBname() { + return bname; + } + + public void setBname(String bname) { + this.bname = bname; + } + + public Boolean getOway() { + return oway; + } + + public void setOway(Boolean oway) { + this.oway = oway; + } + + public String getNs() { + return ns; + } + + public void setNs(String ns) { + this.ns = ns; + } + + public Boolean getNsd() { + return nsd; + } + + public void setNsd(Boolean nsd) { + this.nsd = nsd; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcResponse.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcResponse.java new file mode 100644 index 00000000000..d7e7b17a642 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcResponse.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + } + +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/TopicQueueRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/TopicQueueRequestHeader.java new file mode 100644 index 00000000000..f265dd5c349 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/TopicQueueRequestHeader.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); + +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/TopicRequestHeader.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/TopicRequestHeader.java new file mode 100644 index 00000000000..9f21c07eefa --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/TopicRequestHeader.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpchook/DynamicalExtFieldRPCHook.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpchook/DynamicalExtFieldRPCHook.java new file mode 100644 index 00000000000..25a189e6dd5 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpchook/DynamicalExtFieldRPCHook.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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) { + + } +} diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpchook/StreamTypeRPCHook.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpchook/StreamTypeRPCHook.java new file mode 100644 index 00000000000..501247a7f95 --- /dev/null +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpchook/StreamTypeRPCHook.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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) { + + } +} diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java index e378a7bf1a6..90072960b59 100644 --- a/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java @@ -26,7 +26,12 @@ 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.*; +import org.apache.rocketmq.remoting.netty.ResponseFuture; +import org.apache.rocketmq.remoting.netty.NettyServerConfig; +import org.apache.rocketmq.remoting.netty.NettyClientConfig; +import org.apache.rocketmq.remoting.netty.NettyRemotingServer; +import org.apache.rocketmq.remoting.netty.NettyRemotingClient; +import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.LanguageCode; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.junit.AfterClass; @@ -34,6 +39,7 @@ 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 RemotingServerTest { @@ -43,7 +49,7 @@ public class RemotingServerTest { public static RemotingServer createRemotingServer() throws InterruptedException { NettyServerConfig config = new NettyServerConfig(); RemotingServer remotingServer = new NettyRemotingServer(config); - remotingServer.registerProcessor(0, new AsyncNettyRequestProcessor() { + remotingServer.registerProcessor(0, new NettyRequestProcessor() { @Override public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) { request.setRemark("Hi " + ctx.channel().remoteAddress()); @@ -90,8 +96,8 @@ public void testInvokeSync() throws InterruptedException, RemotingConnectExcepti requestHeader.setCount(1); requestHeader.setMessageTitle("Welcome"); RemotingCommand request = RemotingCommand.createRequestCommand(0, requestHeader); - RemotingCommand response = remotingClient.invokeSync("localhost:8888", request, 1000 * 3); - assertTrue(response != null); + RemotingCommand response = remotingClient.invokeSync("localhost:" + remotingServer.localListenPort(), request, 1000 * 3); + assertNotNull(response); assertThat(response.getLanguage()).isEqualTo(LanguageCode.JAVA); assertThat(response.getExtFields()).hasSize(2); @@ -103,7 +109,7 @@ public void testInvokeOneway() throws InterruptedException, RemotingConnectExcep RemotingCommand request = RemotingCommand.createRequestCommand(0, null); request.setRemark("messi"); - remotingClient.invokeOneway("localhost:8888", request, 1000 * 3); + remotingClient.invokeOneway("localhost:" + remotingServer.localListenPort(), request, 1000 * 3); } @Test @@ -113,7 +119,7 @@ public void testInvokeAsync() throws InterruptedException, RemotingConnectExcept final CountDownLatch latch = new CountDownLatch(1); RemotingCommand request = RemotingCommand.createRequestCommand(0, null); request.setRemark("messi"); - remotingClient.invokeAsync("localhost:8888", request, 1000 * 3, new InvokeCallback() { + remotingClient.invokeAsync("localhost:" + remotingServer.localListenPort(), request, 1000 * 3, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { latch.countDown(); diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/SubRemotingServerTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/SubRemotingServerTest.java new file mode 100644 index 00000000000..43ff1e9c0f6 --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/SubRemotingServerTest.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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); + } + } +} diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java index 13bb17282e3..3da7abf5734 100644 --- a/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java @@ -17,11 +17,20 @@ package org.apache.rocketmq.remoting; +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 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; @@ -65,7 +74,8 @@ 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.junit.Assert.assertTrue; +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertNotNull; @RunWith(MockitoJUnitRunner.class) public class TlsTest { @@ -142,6 +152,10 @@ else if ("noClientAuthFailure".equals(name.getMethodName())) { remotingServer = RemotingServerTest.createRemotingServer(); remotingClient = RemotingServerTest.createRemotingClient(clientConfig); + + await().pollDelay(Duration.ofMillis(10)) + .pollInterval(Duration.ofMillis(10)) + .atMost(20, TimeUnit.SECONDS).until(() -> isHostConnectable(getServerAddress())); } @After @@ -198,7 +212,7 @@ public void serverAcceptsUnAuthClient() throws Exception { @Test public void serverRejectsSSLClient() throws Exception { try { - RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 5); + RemotingCommand response = remotingClient.invokeSync(getServerAddress(), createRequest(), 1000 * 5); failBecauseExceptionWasNotThrown(RemotingSendRequestException.class); } catch (RemotingSendRequestException ignore) { } @@ -211,7 +225,7 @@ public void serverRejectsSSLClient() throws Exception { @Test public void serverRejectsUntrustedClientCert() throws Exception { try { - RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 5); + RemotingCommand response = remotingClient.invokeSync(getServerAddress(), createRequest(), 1000 * 5); failBecauseExceptionWasNotThrown(RemotingSendRequestException.class); } catch (RemotingSendRequestException ignore) { } @@ -229,7 +243,7 @@ public void serverAcceptsUntrustedClientCert() throws Exception { @Test public void noClientAuthFailure() throws Exception { try { - RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 3); + RemotingCommand response = remotingClient.invokeSync(getServerAddress(), createRequest(), 1000 * 3); failBecauseExceptionWasNotThrown(RemotingSendRequestException.class); } catch (RemotingSendRequestException ignore) { } @@ -242,7 +256,7 @@ public void noClientAuthFailure() throws Exception { @Test public void clientRejectsUntrustedServerCert() throws Exception { try { - RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 3); + RemotingCommand response = remotingClient.invokeSync(getServerAddress(), createRequest(), 1000 * 3); failBecauseExceptionWasNotThrown(RemotingSendRequestException.class); } catch (RemotingSendRequestException ignore) { } @@ -301,8 +315,35 @@ private static void writeStringToFile(String path, String content) { } private static String getCertsPath(String fileName) { - File resourcesDirectory = new File("src/test/resources/certs"); - return resourcesDirectory.getAbsolutePath() + "/" + 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() { @@ -317,10 +358,19 @@ private void requestThenAssertResponse() throws Exception { } private void requestThenAssertResponse(RemotingClient remotingClient) throws Exception { - RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 3); - assertTrue(response != null); + 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; + } } diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyClientConfigTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyClientConfigTest.java index 15cf0338649..bc74950829b 100644 --- a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyClientConfigTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyClientConfigTest.java @@ -26,39 +26,41 @@ @RunWith(MockitoJUnitRunner.class) public class NettyClientConfigTest { - @Test - public void testChangeConfigBySystemProperty() throws NoSuchFieldException, IllegalAccessException { - + @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(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); - } + 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); + } } diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstractTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstractTest.java index a272d21da41..8381c132b71 100644 --- a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstractTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstractTest.java @@ -37,7 +37,7 @@ public class NettyRemotingAbstractTest { @Test public void testProcessResponseCommand() throws InterruptedException { final Semaphore semaphore = new Semaphore(0); - ResponseFuture responseFuture = new ResponseFuture(null,1, 3000, new InvokeCallback() { + ResponseFuture responseFuture = new ResponseFuture(null, 1, 3000, new InvokeCallback() { @Override public void operationComplete(final ResponseFuture responseFuture) { assertThat(semaphore.availablePermits()).isEqualTo(0); @@ -58,7 +58,7 @@ public void operationComplete(final ResponseFuture responseFuture) { @Test public void testProcessResponseCommand_NullCallBack() throws InterruptedException { final Semaphore semaphore = new Semaphore(0); - ResponseFuture responseFuture = new ResponseFuture(null,1, 3000, null, + ResponseFuture responseFuture = new ResponseFuture(null, 1, 3000, null, new SemaphoreReleaseOnlyOnce(semaphore)); remotingAbstract.responseTable.putIfAbsent(1, responseFuture); @@ -73,7 +73,7 @@ public void testProcessResponseCommand_NullCallBack() throws InterruptedExceptio @Test public void testProcessResponseCommand_RunCallBackInCurrentThread() throws InterruptedException { final Semaphore semaphore = new Semaphore(0); - ResponseFuture responseFuture = new ResponseFuture(null,1, 3000, new InvokeCallback() { + ResponseFuture responseFuture = new ResponseFuture(null, 1, 3000, new InvokeCallback() { @Override public void operationComplete(final ResponseFuture responseFuture) { assertThat(semaphore.availablePermits()).isEqualTo(0); @@ -105,4 +105,21 @@ public void operationComplete(final ResponseFuture 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, + responseFuture1 -> assertThat(semaphore.availablePermits()).isEqualTo(0), 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); + } } \ No newline at end of file diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java index 4b38ce9524c..efa3eb3d592 100644 --- a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java @@ -16,23 +16,111 @@ */ package org.apache.rocketmq.remoting.netty; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.apache.rocketmq.remoting.InvokeCallback; +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.Spy; import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; +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.Mockito.doAnswer; @RunWith(MockitoJUnitRunner.class) public class NettyRemotingClientTest { + @Spy private NettyRemotingClient remotingClient = new NettyRemotingClient(new NettyClientConfig()); @Test public void testSetCallbackExecutor() throws NoSuchFieldException, IllegalAccessException { 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); + doAnswer(invocation -> { + InvokeCallback callback = invocation.getArgument(3); + ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); + responseFuture.setResponseCommand(response); + callback.operationComplete(responseFuture); + return null; + }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); + + 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); + doAnswer(invocation -> { + InvokeCallback callback = invocation.getArgument(3); + ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); + responseFuture.setSendRequestOK(false); + callback.operationComplete(responseFuture); + return null; + }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); + + 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); + doAnswer(invocation -> { + InvokeCallback callback = invocation.getArgument(3); + ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), -1L, null, null); + callback.operationComplete(responseFuture); + return null; + }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); + + 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); + + RemotingCommand response = RemotingCommand.createResponseCommand(null); + response.setCode(ResponseCode.SUCCESS); + doAnswer(invocation -> { + InvokeCallback callback = invocation.getArgument(3); + ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); + callback.operationComplete(responseFuture); + return null; + }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); + + CompletableFuture future = remotingClient.invoke("0.0.0.0", request, 1000); + Throwable thrown = catchThrowable(future::get); + assertThat(thrown.getCause()).isInstanceOf(RemotingException.class); + } } diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyServerConfigTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyServerConfigTest.java index c07025d63f9..0ab0d193e8a 100644 --- a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyServerConfigTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyServerConfigTest.java @@ -26,12 +26,12 @@ @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); - } + @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); + } } diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/RemotingCodeDistributionHandlerTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/RemotingCodeDistributionHandlerTest.java new file mode 100644 index 00000000000..eb623a9de92 --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/RemotingCodeDistributionHandlerTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + }); + } +} \ No newline at end of file diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/CheckpointFileTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/CheckpointFileTest.java new file mode 100644 index 00000000000..658f59e4806 --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/CheckpointFileTest.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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); + } +} diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/ClusterInfoTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/ClusterInfoTest.java similarity index 87% rename from common/src/test/java/org/apache/rocketmq/common/protocol/ClusterInfoTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/ClusterInfoTest.java index bfdd872153c..7d31931d5e8 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/ClusterInfoTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/ClusterInfoTest.java @@ -15,20 +15,20 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol; - -import org.apache.rocketmq.common.MixAll; -import org.apache.rocketmq.common.protocol.body.ClusterInfo; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; - -import static org.junit.Assert.*; - -import org.junit.Test; +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 { @@ -71,8 +71,8 @@ public void testRetrieveAllAddrByCluster() throws Exception { private ClusterInfo buildClusterInfo() throws Exception { ClusterInfo clusterInfo = new ClusterInfo(); - HashMap brokerAddrTable = new HashMap(); - HashMap> clusterAddrTable = new HashMap>(); + HashMap brokerAddrTable = new HashMap<>(); + HashMap> clusterAddrTable = new HashMap<>(); //build brokerData BrokerData brokerData = new BrokerData(); @@ -80,13 +80,13 @@ private ClusterInfo buildClusterInfo() throws Exception { brokerData.setCluster("DEFAULT_CLUSTER"); //build brokerAddrs - HashMap brokerAddrs = new HashMap(); + HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(MixAll.MASTER_ID, MixAll.getLocalhostByNetworkInterface()); brokerData.setBrokerAddrs(brokerAddrs); brokerAddrTable.put("master", brokerData); - Set brokerNames = new HashSet(); + Set brokerNames = new HashSet<>(); brokerNames.add("master"); clusterAddrTable.put("DEFAULT_CLUSTER", brokerNames); @@ -95,4 +95,4 @@ private ClusterInfo buildClusterInfo() throws Exception { clusterInfo.setClusterAddrTable(clusterAddrTable); return clusterInfo; } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/ConsumeStatusTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/ConsumeStatusTest.java similarity index 90% rename from common/src/test/java/org/apache/rocketmq/common/protocol/ConsumeStatusTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/ConsumeStatusTest.java index 4a2e790fe66..b685d31311a 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/ConsumeStatusTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/ConsumeStatusTest.java @@ -15,10 +15,9 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol; +package org.apache.rocketmq.remoting.protocol; -import org.apache.rocketmq.common.protocol.body.ConsumeStatus; -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; +import org.apache.rocketmq.remoting.protocol.body.ConsumeStatus; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/common/src/test/java/org/apache/rocketmq/common/DataVersionTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/DataVersionTest.java similarity index 90% rename from common/src/test/java/org/apache/rocketmq/common/DataVersionTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/DataVersionTest.java index f4d14e5614f..dccedde491c 100644 --- a/common/src/test/java/org/apache/rocketmq/common/DataVersionTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/DataVersionTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common; +package org.apache.rocketmq.remoting.protocol; import java.util.concurrent.atomic.AtomicLong; import org.junit.Assert; @@ -67,4 +67,11 @@ public void testEquals_trueWhenCountersBothNull() { other.setTimestamp(dataVersion.getTimestamp()); Assert.assertTrue(dataVersion.equals(other)); } -} \ No newline at end of file + + @Test + public void testEncode() { + DataVersion dataVersion = new DataVersion(); + Assert.assertTrue(dataVersion.encode().length > 0); + Assert.assertNotNull(dataVersion.toJson()); + } +} diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/GroupListTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/GroupListTest.java similarity index 83% rename from common/src/test/java/org/apache/rocketmq/common/protocol/GroupListTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/GroupListTest.java index 854ff05ddf4..e0fba128d17 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/GroupListTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/GroupListTest.java @@ -15,13 +15,12 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol; - -import org.apache.rocketmq.common.protocol.body.GroupList; -import org.junit.Test; +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; @@ -32,10 +31,10 @@ public class GroupListTest { @Test public void testSetGet() throws Exception { - HashSet fisrtUniqueSet=createUniqueNewSet(); - HashSet secondUniqueSet=createUniqueNewSet(); + HashSet fisrtUniqueSet = createUniqueNewSet(); + HashSet secondUniqueSet = createUniqueNewSet(); assertThat(fisrtUniqueSet).isNotEqualTo(secondUniqueSet); - GroupList gl=new GroupList(); + GroupList gl = new GroupList(); gl.setGroupList(fisrtUniqueSet); assertThat(gl.getGroupList()).isEqualTo(fisrtUniqueSet); assertThat(gl.getGroupList()).isNotEqualTo(secondUniqueSet); @@ -45,7 +44,7 @@ public void testSetGet() throws Exception { } private HashSet createUniqueNewSet() { - HashSet groups=new HashSet(); + HashSet groups = new HashSet<>(); groups.add(UUID.randomUUID().toString()); return groups; } diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/LanguageCodeTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/LanguageCodeTest.java new file mode 100644 index 00000000000..e1f9016cc38 --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/LanguageCodeTest.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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); + } + +} diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/NamespaceUtilTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/NamespaceUtilTest.java similarity index 97% rename from common/src/test/java/org/apache/rocketmq/common/protocol/NamespaceUtilTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/NamespaceUtilTest.java index 7e470d949d6..2f7af7adff6 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/NamespaceUtilTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/NamespaceUtilTest.java @@ -15,14 +15,14 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol; +package org.apache.rocketmq.remoting.protocol; import org.apache.rocketmq.common.MixAll; import org.junit.Assert; import org.junit.Test; /** - * @author MQDevelopers + * MQDevelopers */ public class NamespaceUtilTest { @@ -75,14 +75,14 @@ public void testWrapNamespace() { 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 ); + 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(){ + public void testGetNamespaceFromResource() { String namespaceExpectBlank = NamespaceUtil.getNamespaceFromResource(TOPIC); Assert.assertEquals(namespaceExpectBlank, NamespaceUtil.STRING_BLANK); String namespace = NamespaceUtil.getNamespaceFromResource(TOPIC_WITH_NAMESPACE); @@ -90,4 +90,4 @@ public void testGetNamespaceFromResource(){ String namespaceFromRetryTopic = NamespaceUtil.getNamespaceFromResource(RETRY_TOPIC_WITH_NAMESPACE); Assert.assertEquals(namespaceFromRetryTopic, INSTANCE_ID); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/QueryConsumeTimeSpanBodyTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/QueryConsumeTimeSpanBodyTest.java similarity index 89% rename from common/src/test/java/org/apache/rocketmq/common/protocol/QueryConsumeTimeSpanBodyTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/QueryConsumeTimeSpanBodyTest.java index 76844d91cee..6460d80a2cd 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/QueryConsumeTimeSpanBodyTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/QueryConsumeTimeSpanBodyTest.java @@ -15,19 +15,17 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol; - -import org.apache.rocketmq.common.UtilAll; -import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.common.protocol.body.QueryConsumeTimeSpanBody; -import org.apache.rocketmq.common.protocol.body.QueueTimeSpan; -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; -import org.junit.Test; +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; @@ -49,21 +47,21 @@ public void testSetGet() throws Exception { @Test public void testFromJson() throws Exception { QueryConsumeTimeSpanBody qctsb = new QueryConsumeTimeSpanBody(); - List queueTimeSpans = new ArrayList(); + List queueTimeSpans = new ArrayList<>(); QueueTimeSpan queueTimeSpan = new QueueTimeSpan(); - queueTimeSpan.setMinTimeStamp(1550825710000l); - queueTimeSpan.setMaxTimeStamp(1550825790000l); - queueTimeSpan.setConsumeTimeStamp(1550825760000l); - queueTimeSpan.setDelayTime(5000l); + 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).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); } @@ -100,12 +98,12 @@ public void testEncode() throws Exception { } private List newUniqueConsumeTimeSpanSet() { - List queueTimeSpans = new ArrayList(); + List queueTimeSpans = new ArrayList<>(); QueueTimeSpan queueTimeSpan = new QueueTimeSpan(); queueTimeSpan.setMinTimeStamp(System.currentTimeMillis()); queueTimeSpan.setMaxTimeStamp(UtilAll.computeNextHourTimeMillis()); queueTimeSpan.setConsumeTimeStamp(UtilAll.computeNextMinutesTimeMillis()); - queueTimeSpan.setDelayTime(5000l); + queueTimeSpan.setDelayTime(5000L); MessageQueue messageQueue = new MessageQueue(UUID.randomUUID().toString(), UUID.randomUUID().toString(), new Random().nextInt()); queueTimeSpan.setMessageQueue(messageQueue); queueTimeSpans.add(queueTimeSpan); diff --git a/common/src/test/java/org/apache/rocketmq/common/RegisterBrokerBodyTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RegisterBrokerBodyTest.java similarity index 79% rename from common/src/test/java/org/apache/rocketmq/common/RegisterBrokerBodyTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RegisterBrokerBodyTest.java index 87a0fc0085d..e63552f9f75 100644 --- a/common/src/test/java/org/apache/rocketmq/common/RegisterBrokerBodyTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RegisterBrokerBodyTest.java @@ -15,24 +15,28 @@ * limitations under the License. */ -package org.apache.rocketmq.common; +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.protocol.body.RegisterBrokerBody; -import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper; -import static org.junit.Assert.assertEquals; + +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(); - TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); + TopicConfigAndMappingSerializeWrapper topicConfigSerializeWrapper = new TopicConfigAndMappingSerializeWrapper(); registerBrokerBody.setTopicConfigSerializeWrapper(topicConfigSerializeWrapper); - - ConcurrentMap topicConfigTable = new ConcurrentHashMap(); + + ConcurrentMap topicConfigTable = new ConcurrentHashMap<>(); for (int i = 0; i < 10000; i++) { topicConfigTable.put(String.valueOf(i), new TopicConfig(String.valueOf(i))); } @@ -41,9 +45,7 @@ public void test_encode_decode() throws IOException { byte[] compareEncode = registerBrokerBody.encode(true); byte[] encode2 = registerBrokerBody.encode(false); - System.out.println(compareEncode.length); - System.out.println(encode2.length); - RegisterBrokerBody decodeRegisterBrokerBody = RegisterBrokerBody.decode(compareEncode, true); + RegisterBrokerBody decodeRegisterBrokerBody = RegisterBrokerBody.decode(compareEncode, true, MQVersion.Version.V5_0_0); assertEquals(registerBrokerBody.getTopicConfigSerializeWrapper().getTopicConfigTable().size(), decodeRegisterBrokerBody.getTopicConfigSerializeWrapper().getTopicConfigTable().size()); diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RemotingCommandTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RemotingCommandTest.java index dd77c32e64b..b5a0d003ebc 100644 --- a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RemotingCommandTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RemotingCommandTest.java @@ -19,6 +19,8 @@ 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; @@ -32,7 +34,13 @@ public class RemotingCommandTest { public void testMarkProtocolType_JSONProtocolType() { int source = 261; SerializeType type = SerializeType.JSON; - byte[] result = RemotingCommand.markProtocolType(source, type); + + 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}); } @@ -40,7 +48,12 @@ public void testMarkProtocolType_JSONProtocolType() { public void testMarkProtocolType_ROCKETMQProtocolType() { int source = 16777215; SerializeType type = SerializeType.ROCKETMQ; - byte[] result = RemotingCommand.markProtocolType(source, type); + 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}); } @@ -48,7 +61,7 @@ public void testMarkProtocolType_ROCKETMQProtocolType() { public void testCreateRequestCommand_RegisterBroker() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); - int code = 103; //org.apache.rocketmq.common.protocol.RequestCode.REGISTER_BROKER + 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); @@ -107,7 +120,7 @@ public void testCreateResponseCommand_SystemError() { public void testEncodeAndDecode_EmptyBody() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); - int code = 103; //org.apache.rocketmq.common.protocol.RequestCode.REGISTER_BROKER + int code = 103; //org.apache.rocketmq.remoting.protocol.RequestCode.REGISTER_BROKER CommandCustomHeader header = new SampleCommandCustomHeader(); RemotingCommand cmd = RemotingCommand.createRequestCommand(code, header); @@ -136,7 +149,7 @@ public void testEncodeAndDecode_EmptyBody() { public void testEncodeAndDecode_FilledBody() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); - int code = 103; //org.apache.rocketmq.common.protocol.RequestCode.REGISTER_BROKER + 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}); @@ -165,7 +178,7 @@ public void testEncodeAndDecode_FilledBody() { public void testEncodeAndDecode_FilledBodyWithExtFields() throws RemotingCommandException { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); - int code = 103; //org.apache.rocketmq.common.protocol.RequestCode.REGISTER_BROKER + int code = 103; //org.apache.rocketmq.remoting.protocol.RequestCode.REGISTER_BROKER CommandCustomHeader header = new ExtFieldsHeader(); RemotingCommand cmd = RemotingCommand.createRequestCommand(code, header); @@ -194,7 +207,7 @@ public void testEncodeAndDecode_FilledBodyWithExtFields() throws RemotingCommand 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).getLongValue()).isEqualTo(23333333L); assertThat(((ExtFieldsHeader) decodedHeader).isBooleanValue()).isEqualTo(true); assertThat(((ExtFieldsHeader) decodedHeader).getDoubleValue()).isBetween(0.617, 0.619); } catch (RemotingCommandException e) { @@ -220,6 +233,32 @@ public void testNotNullField() throws Exception { 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 { @@ -241,7 +280,7 @@ public void checkFields() throws RemotingCommandException { class ExtFieldsHeader implements CommandCustomHeader { private String stringValue = "bilibili"; private int intValue = 2333; - private long longValue = 23333333l; + private long longValue = 23333333L; private boolean booleanValue = true; private double doubleValue = 0.618; @@ -268,4 +307,72 @@ public boolean isBooleanValue() { public double getDoubleValue() { return doubleValue; } -} \ No newline at end of file + + @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; + } +} diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RemotingSerializableTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RemotingSerializableTest.java index 3e8b7a90ae6..6bd80217da0 100644 --- a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RemotingSerializableTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RemotingSerializableTest.java @@ -16,9 +16,19 @@ */ package org.apache.rocketmq.remoting.protocol; +import com.alibaba.fastjson.serializer.SerializerFeature; +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 org.junit.Test; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -80,6 +90,38 @@ public void setStringList(List stringList) { "}"); } + @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(SerializerFeature.BrowserCompatible, SerializerFeature.QuoteFieldNames, + SerializerFeature.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 { diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RequestSourceTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RequestSourceTest.java new file mode 100644 index 00000000000..b2ed1e3419c --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RequestSourceTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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)); + } +} diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RequestTypeTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RequestTypeTest.java new file mode 100644 index 00000000000..7457926d721 --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RequestTypeTest.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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(); + } +} \ No newline at end of file diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RocketMQSerializableTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RocketMQSerializableTest.java index 5732b9596d1..7cf32d70c34 100644 --- a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RocketMQSerializableTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RocketMQSerializableTest.java @@ -16,7 +16,11 @@ */ 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; @@ -28,7 +32,7 @@ public class RocketMQSerializableTest { public void testRocketMQProtocolEncodeAndDecode_WithoutRemarkWithoutExtFields() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); - //org.apache.rocketmq.common.protocol.RequestCode.REGISTER_BROKER + //org.apache.rocketmq.remoting.protocol.RequestCode.REGISTER_BROKER int code = 103; RemotingCommand cmd = RemotingCommand.createRequestCommand(code, new SampleCommandCustomHeader()); cmd.setSerializeTypeCurrentRPC(SerializeType.ROCKETMQ); @@ -46,7 +50,7 @@ public void testRocketMQProtocolEncodeAndDecode_WithoutRemarkWithoutExtFields() RemotingCommand decodedCommand = null; try { - decodedCommand = RocketMQSerializable.rocketMQProtocolDecode(result); + decodedCommand = RocketMQSerializable.rocketMQProtocolDecode(Unpooled.wrappedBuffer(result), result.length); assertThat(decodedCommand.getCode()).isEqualTo(code); assertThat(decodedCommand.getLanguage()).isEqualTo(LanguageCode.JAVA); @@ -66,7 +70,7 @@ public void testRocketMQProtocolEncodeAndDecode_WithoutRemarkWithoutExtFields() public void testRocketMQProtocolEncodeAndDecode_WithRemarkWithoutExtFields() { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); - //org.apache.rocketmq.common.protocol.RequestCode.REGISTER_BROKER + //org.apache.rocketmq.remoting.protocol.RequestCode.REGISTER_BROKER int code = 103; RemotingCommand cmd = RemotingCommand.createRequestCommand(code, new SampleCommandCustomHeader()); @@ -90,7 +94,7 @@ public void testRocketMQProtocolEncodeAndDecode_WithRemarkWithoutExtFields() { assertThat(parseToInt(result, 30)).isEqualTo(0); //empty extFields try { - RemotingCommand decodedCommand = RocketMQSerializable.rocketMQProtocolDecode(result); + RemotingCommand decodedCommand = RocketMQSerializable.rocketMQProtocolDecode(Unpooled.wrappedBuffer(result), result.length); assertThat(decodedCommand.getCode()).isEqualTo(code); assertThat(decodedCommand.getLanguage()).isEqualTo(LanguageCode.JAVA); @@ -107,10 +111,10 @@ public void testRocketMQProtocolEncodeAndDecode_WithRemarkWithoutExtFields() { } @Test - public void testRocketMQProtocolEncodeAndDecode_WithoutRemarkWithExtFields() { + public void testRocketMQProtocolEncodeAndDecode_WithoutRemarkWithExtFields() throws Exception { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, "2333"); - //org.apache.rocketmq.common.protocol.RequestCode.REGISTER_BROKER + //org.apache.rocketmq.remoting.protocol.RequestCode.REGISTER_BROKER int code = 103; RemotingCommand cmd = RemotingCommand.createRequestCommand(code, new SampleCommandCustomHeader()); @@ -130,11 +134,12 @@ public void testRocketMQProtocolEncodeAndDecode_WithoutRemarkWithExtFields() { byte[] extFieldsArray = new byte[14]; System.arraycopy(result, 21, extFieldsArray, 0, 14); - HashMap extFields = RocketMQSerializable.mapDeserialize(extFieldsArray); + HashMap extFields = + RocketMQSerializable.mapDeserialize(Unpooled.wrappedBuffer(extFieldsArray), extFieldsArray.length); assertThat(extFields).contains(new HashMap.SimpleEntry("key", "value")); try { - RemotingCommand decodedCommand = RocketMQSerializable.rocketMQProtocolDecode(result); + 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); @@ -149,19 +154,6 @@ public void testRocketMQProtocolEncodeAndDecode_WithoutRemarkWithExtFields() { } } - @Test - public void testIsBlank_NotBlank() { - assertThat(RocketMQSerializable.isBlank("bar")).isFalse(); - assertThat(RocketMQSerializable.isBlank(" A ")).isFalse(); - } - - @Test - public void testIsBlank_Blank() { - assertThat(RocketMQSerializable.isBlank(null)).isTrue(); - assertThat(RocketMQSerializable.isBlank("")).isTrue(); - assertThat(RocketMQSerializable.isBlank(" ")).isTrue(); - } - private short parseToShort(byte[] array, int index) { return (short) (array[index] * 256 + array[++index]); } @@ -170,4 +162,56 @@ private int parseToInt(byte[] array, int index) { return array[index] * 16777216 + array[++index] * 65536 + array[++index] * 256 + array[++index]; } -} \ No newline at end of file + + 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); + } +} diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/admin/ConsumeStatsTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/admin/ConsumeStatsTest.java new file mode 100644 index 00000000000..10021c40805 --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/admin/ConsumeStatsTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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()); + } +} diff --git a/common/src/test/java/org/apache/rocketmq/common/admin/TopicStatsTableTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/admin/TopicStatsTableTest.java similarity index 97% rename from common/src/test/java/org/apache/rocketmq/common/admin/TopicStatsTableTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/admin/TopicStatsTableTest.java index 22ea926af8c..bb828dbf8bd 100644 --- a/common/src/test/java/org/apache/rocketmq/common/admin/TopicStatsTableTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/admin/TopicStatsTableTest.java @@ -14,17 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.common.admin; +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; -import java.util.HashMap; -import java.util.Map; - public class TopicStatsTableTest { @@ -44,7 +43,7 @@ public class TopicStatsTableTest { @Before public void buildTopicStatsTable() { - HashMap offsetTableMap = new HashMap(); + HashMap offsetTableMap = new HashMap<>(); MessageQueue messageQueue = new MessageQueue(TEST_TOPIC, TEST_BROKER, QUEUE_ID); diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/BatchAckTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/BatchAckTest.java new file mode 100644 index 00000000000..427a132d646 --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/BatchAckTest.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.fastjson.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(); + } + } + } +} diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/body/BrokerStatsDataTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/BrokerStatsDataTest.java similarity index 98% rename from common/src/test/java/org/apache/rocketmq/common/protocol/body/BrokerStatsDataTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/BrokerStatsDataTest.java index 0ad8cb984cd..cb1faef8681 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/body/BrokerStatsDataTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/BrokerStatsDataTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.junit.Test; diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/body/CheckClientRequestBodyTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/CheckClientRequestBodyTest.java similarity index 93% rename from common/src/test/java/org/apache/rocketmq/common/protocol/body/CheckClientRequestBodyTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/CheckClientRequestBodyTest.java index 22bc6b39ddc..402ca58f2b8 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/body/CheckClientRequestBodyTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/CheckClientRequestBodyTest.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; 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; @@ -40,4 +40,4 @@ public void testFromJson() { assertThat(fromJson.getGroup()).isEqualTo(expectedGroup); assertThat(fromJson.getSubscriptionData()).isEqualTo(subscriptionData); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/body/ConsumeMessageDirectlyResultTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumeMessageDirectlyResultTest.java similarity index 97% rename from common/src/test/java/org/apache/rocketmq/common/protocol/body/ConsumeMessageDirectlyResultTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumeMessageDirectlyResultTest.java index 15fc4b22ab5..5e9d385eaec 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/body/ConsumeMessageDirectlyResultTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumeMessageDirectlyResultTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.junit.Test; diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/body/ConsumeStatsListTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumeStatsListTest.java similarity index 89% rename from common/src/test/java/org/apache/rocketmq/common/protocol/body/ConsumeStatsListTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumeStatsListTest.java index 088ca05ee63..01a4506bfbf 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/body/ConsumeStatsListTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumeStatsListTest.java @@ -15,16 +15,15 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; - -import org.apache.rocketmq.common.admin.ConsumeStats; -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; -import org.junit.Test; +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; @@ -33,11 +32,11 @@ public class ConsumeStatsListTest { @Test public void testFromJson() { ConsumeStats consumeStats = new ConsumeStats(); - ArrayList consumeStatsListValue = new ArrayList(); + ArrayList consumeStatsListValue = new ArrayList<>(); consumeStatsListValue.add(consumeStats); - HashMap> map = new HashMap>(); + HashMap> map = new HashMap<>(); map.put("subscriptionGroupName", consumeStatsListValue); - List>> consumeStatsListValue2 = new ArrayList>>(); + List>> consumeStatsListValue2 = new ArrayList<>(); consumeStatsListValue2.add(map); String brokerAddr = "brokerAddr"; @@ -59,4 +58,4 @@ public void testFromJson() { ConsumeStats fromJsonConsumeStats = fromJsonConsumeStatsList.get(0).get("subscriptionGroupName").get(0); assertThat(fromJsonConsumeStats).isExactlyInstanceOf(ConsumeStats.class); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/body/ConsumerConnectionTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumerConnectionTest.java similarity index 85% rename from common/src/test/java/org/apache/rocketmq/common/protocol/body/ConsumerConnectionTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumerConnectionTest.java index be1460ebcad..e9d2fee1232 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/body/ConsumerConnectionTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumerConnectionTest.java @@ -15,18 +15,18 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; - -import org.apache.rocketmq.common.consumer.ConsumeFromWhere; -import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; -import org.junit.Test; +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; @@ -35,11 +35,11 @@ public class ConsumerConnectionTest { @Test public void testFromJson() { ConsumerConnection consumerConnection = new ConsumerConnection(); - HashSet connections = new HashSet(); + HashSet connections = new HashSet<>(); Connection conn = new Connection(); connections.add(conn); - ConcurrentHashMap subscriptionTable = new ConcurrentHashMap(); + ConcurrentHashMap subscriptionTable = new ConcurrentHashMap<>(); SubscriptionData subscriptionData = new SubscriptionData(); subscriptionTable.put("topicA", subscriptionData); @@ -59,7 +59,7 @@ public void testFromJson() { assertThat(fromJson.getMessageModel()).isEqualTo(MessageModel.CLUSTERING); HashSet connectionSet = fromJson.getConnectionSet(); - assertThat(connectionSet).isInstanceOf(Set.class); + Assertions.assertThat(connectionSet).isInstanceOf(Set.class); SubscriptionData data = fromJson.getSubscriptionTable().get("topicA"); assertThat(data).isExactlyInstanceOf(SubscriptionData.class); @@ -68,7 +68,7 @@ public void testFromJson() { @Test public void testComputeMinVersion() { ConsumerConnection consumerConnection = new ConsumerConnection(); - HashSet connections = new HashSet(); + HashSet connections = new HashSet<>(); Connection conn1 = new Connection(); conn1.setVersion(1); connections.add(conn1); diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/body/ConsumerRunningInfoTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumerRunningInfoTest.java similarity index 87% rename from common/src/test/java/org/apache/rocketmq/common/protocol/body/ConsumerRunningInfoTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumerRunningInfoTest.java index b37189383a3..f05de389df3 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/body/ConsumerRunningInfoTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ConsumerRunningInfoTest.java @@ -15,21 +15,19 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +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.common.protocol.heartbeat.ConsumeType; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; 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 java.util.Properties; -import java.util.TreeMap; -import java.util.TreeSet; - -import static org.apache.rocketmq.common.protocol.heartbeat.ConsumeType.CONSUME_ACTIVELY; - +import static org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType.CONSUME_ACTIVELY; import static org.assertj.core.api.Assertions.assertThat; public class ConsumerRunningInfoTest { @@ -45,16 +43,16 @@ public void init() { consumerRunningInfo = new ConsumerRunningInfo(); consumerRunningInfo.setJstack("test"); - TreeMap mqTable = new TreeMap(); + TreeMap mqTable = new TreeMap<>(); messageQueue = new MessageQueue("topicA","broker", 1); mqTable.put(messageQueue, new ProcessQueueInfo()); consumerRunningInfo.setMqTable(mqTable); - TreeMap statusTable = new TreeMap(); + TreeMap statusTable = new TreeMap<>(); statusTable.put("topicA", new ConsumeStatus()); consumerRunningInfo.setStatusTable(statusTable); - TreeSet subscriptionSet = new TreeSet(); + TreeSet subscriptionSet = new TreeSet<>(); subscriptionSet.add(new SubscriptionData()); consumerRunningInfo.setSubscriptionSet(subscriptionSet); @@ -63,7 +61,7 @@ public void init() { properties.put(ConsumerRunningInfo.PROP_CONSUMER_START_TIMESTAMP, System.currentTimeMillis()); consumerRunningInfo.setProperties(properties); - criTable = new TreeMap(); + criTable = new TreeMap<>(); criTable.put("client_id", consumerRunningInfo); } @@ -86,20 +84,20 @@ public void testFromJson() { } @Test - public void testAnalyzeRebalance(){ + public void testAnalyzeRebalance() { boolean result = ConsumerRunningInfo.analyzeRebalance(criTable); assertThat(result).isTrue(); } @Test - public void testAnalyzeProcessQueue(){ + public void testAnalyzeProcessQueue() { String result = ConsumerRunningInfo.analyzeProcessQueue("client_id", consumerRunningInfo); assertThat(result).isEmpty(); } @Test - public void testAnalyzeSubscription(){ + public void testAnalyzeSubscription() { boolean result = ConsumerRunningInfo.analyzeSubscription(criTable); assertThat(result).isTrue(); } diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/body/KVTableTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/KVTableTest.java similarity index 91% rename from common/src/test/java/org/apache/rocketmq/common/protocol/body/KVTableTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/KVTableTest.java index 836733c5069..61482132e30 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/body/KVTableTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/KVTableTest.java @@ -15,21 +15,19 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; +import java.util.HashMap; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; -import org.junit.Assert; import org.junit.Test; -import java.util.HashMap; - import static org.assertj.core.api.Assertions.assertThat; public class KVTableTest { @Test public void testFromJson() throws Exception { - HashMap table = new HashMap(); + HashMap table = new HashMap<>(); table.put("key1", "value1"); table.put("key2", "value2"); diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/MessageRequestModeSerializeWrapperTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/MessageRequestModeSerializeWrapperTest.java new file mode 100644 index 00000000000..6ae3dbd3a0b --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/MessageRequestModeSerializeWrapperTest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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); + } +} diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/body/QueryConsumeQueueResponseBodyTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/QueryConsumeQueueResponseBodyTest.java similarity index 91% rename from common/src/test/java/org/apache/rocketmq/common/protocol/body/QueryConsumeQueueResponseBodyTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/QueryConsumeQueueResponseBodyTest.java index fad86f71f32..7e44d710b4e 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/body/QueryConsumeQueueResponseBodyTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/QueryConsumeQueueResponseBodyTest.java @@ -15,21 +15,20 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; - -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; -import org.junit.Test; +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(){ + public void test() { QueryConsumeQueueResponseBody body = new QueryConsumeQueueResponseBody(); SubscriptionData subscriptionData = new SubscriptionData(); @@ -40,7 +39,7 @@ public void test(){ data.setPhysicOffset(10L); data.setPhysicSize(1); data.setTagsCode(1L); - List list = new ArrayList(); + List list = new ArrayList<>(); list.add(data); body.setQueueData(list); @@ -51,7 +50,6 @@ public void test(){ String json = RemotingSerializable.toJson(body, true); QueryConsumeQueueResponseBody fromJson = RemotingSerializable.fromJson(json, QueryConsumeQueueResponseBody.class); - System.out.println(json); //test ConsumeQueue ConsumeQueueData jsonData = fromJson.getQueueData().get(0); assertThat(jsonData.getMsg()).isEqualTo("this is default msg"); diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/body/QueryCorrectionOffsetBodyTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/QueryCorrectionOffsetBodyTest.java similarity index 93% rename from common/src/test/java/org/apache/rocketmq/common/protocol/body/QueryCorrectionOffsetBodyTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/QueryCorrectionOffsetBodyTest.java index d0c4b5026c7..6d62b07f7ac 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/body/QueryCorrectionOffsetBodyTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/QueryCorrectionOffsetBodyTest.java @@ -15,13 +15,12 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; - -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; -import org.junit.Test; +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; @@ -30,7 +29,7 @@ public class QueryCorrectionOffsetBodyTest { @Test public void testFromJson() throws Exception { QueryCorrectionOffsetBody qcob = new QueryCorrectionOffsetBody(); - Map offsetMap = new HashMap(); + Map offsetMap = new HashMap<>(); offsetMap.put(1, 100L); offsetMap.put(2, 200L); qcob.setCorrectionOffsets(offsetMap); diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/body/ResetOffsetBodyTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ResetOffsetBodyTest.java similarity index 93% rename from common/src/test/java/org/apache/rocketmq/common/protocol/body/ResetOffsetBodyTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ResetOffsetBodyTest.java index f9559a92c10..115749947b6 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/body/ResetOffsetBodyTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/ResetOffsetBodyTest.java @@ -15,15 +15,14 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +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 java.util.HashMap; -import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; public class ResetOffsetBodyTest { @@ -31,7 +30,7 @@ public class ResetOffsetBodyTest { @Test public void testFromJson() throws Exception { ResetOffsetBody rob = new ResetOffsetBody(); - Map offsetMap = new HashMap(); + Map offsetMap = new HashMap<>(); MessageQueue queue = new MessageQueue(); queue.setQueueId(1); queue.setBrokerName("brokerName"); diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/body/SubscriptionGroupWrapperTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/SubscriptionGroupWrapperTest.java similarity index 89% rename from common/src/test/java/org/apache/rocketmq/common/protocol/body/SubscriptionGroupWrapperTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/SubscriptionGroupWrapperTest.java index ffd7f61022a..b7d89a21bfb 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/body/SubscriptionGroupWrapperTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/body/SubscriptionGroupWrapperTest.java @@ -15,21 +15,22 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.body; +package org.apache.rocketmq.remoting.protocol.body; -import org.apache.rocketmq.common.DataVersion; -import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig; +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 java.util.concurrent.ConcurrentHashMap; + import static org.assertj.core.api.Assertions.assertThat; public class SubscriptionGroupWrapperTest { @Test - public void testFromJson(){ + public void testFromJson() { SubscriptionGroupWrapper subscriptionGroupWrapper = new SubscriptionGroupWrapper(); - ConcurrentHashMap subscriptions = new ConcurrentHashMap(); + ConcurrentHashMap subscriptions = new ConcurrentHashMap<>(); SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig(); subscriptionGroupConfig.setConsumeBroadcastEnable(true); subscriptionGroupConfig.setBrokerId(1234); diff --git a/common/src/test/java/org/apache/rocketmq/common/filter/FilterAPITest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/filter/FilterAPITest.java similarity index 92% rename from common/src/test/java/org/apache/rocketmq/common/filter/FilterAPITest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/filter/FilterAPITest.java index 5190f88f47b..002a1badc3e 100644 --- a/common/src/test/java/org/apache/rocketmq/common/filter/FilterAPITest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/filter/FilterAPITest.java @@ -15,13 +15,13 @@ * limitations under the License. */ -package org.apache.rocketmq.common.filter; - -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; -import org.junit.Test; +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; @@ -37,7 +37,7 @@ public void testBuildSubscriptionData() throws Exception { assertThat(subscriptionData.getTopic()).isEqualTo(topic); assertThat(subscriptionData.getSubString()).isEqualTo(subString); String[] tags = subString.split("\\|\\|"); - Set tagSet = new HashSet(); + Set tagSet = new HashSet<>(); for (String tag : tags) { tagSet.add(tag.trim()); } @@ -57,7 +57,7 @@ public void testBuildTagSome() { assertThat(ExpressionType.isTagType(subscriptionData.getExpressionType())).isTrue(); assertThat(subscriptionData.getTagsSet()).isNotNull(); - assertThat(subscriptionData.getTagsSet()).containsExactly("A", "B"); + assertThat(subscriptionData.getTagsSet()).containsExactlyInAnyOrder("A", "B"); } catch (Exception e) { e.printStackTrace(); assertThat(Boolean.FALSE).isTrue(); diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/header/ExtraInfoUtilTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/header/ExtraInfoUtilTest.java new file mode 100644 index 00000000000..7cf258711cf --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/header/ExtraInfoUtilTest.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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, false, queueId, queueIdCount); + ExtraInfoUtil.buildQueueOffsetOrderCountInfo(sb, false, queueId, queueOffset, queueOffsetCount); + Map orderCountInfo = ExtraInfoUtil.parseOrderCountInfo(sb.toString()); + + assertEquals(queueIdCount, orderCountInfo.get(queueIdKey)); + assertEquals(queueOffsetCount, orderCountInfo.get(queueOffsetKey)); + } +} diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessorTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/header/FastCodesHeaderTest.java similarity index 72% rename from broker/src/test/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessorTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/header/FastCodesHeaderTest.java index da2611bd546..6bb100f574f 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessorTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/header/FastCodesHeaderTest.java @@ -14,24 +14,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.rocketmq.broker.processor; +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 java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeaderV2; +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 AbstractSendMessageProcessorTest { +public class FastCodesHeaderTest { + @Test - public void testDecodeSendMessageHeaderV2() throws Exception { - Field[] declaredFields = SendMessageRequestHeaderV2.class.getDeclaredFields(); + 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("$")) { @@ -43,7 +49,7 @@ public void testDecodeSendMessageHeaderV2() throws Exception { RemotingCommand command = RemotingCommand.createRequestCommand(0, null); HashMap m = buildExtFields(declaredFieldsList); command.setExtFields(m); - check(command, declaredFieldsList); + check(command, declaredFieldsList, classHeader); } private HashMap buildExtFields(List fields) { @@ -65,9 +71,11 @@ private HashMap buildExtFields(List fields) { return extFields; } - private void check(RemotingCommand command, List fields) throws Exception { - SendMessageRequestHeaderV2 o1 = (SendMessageRequestHeaderV2) command.decodeCommandCustomHeader(SendMessageRequestHeaderV2.class); - SendMessageRequestHeaderV2 o2 = AbstractSendMessageProcessor.decodeSendMessageHeaderV2(command); + private void check(RemotingCommand command, List fields, + Class classHeader) throws Exception { + CommandCustomHeader o1 = command.decodeCommandCustomHeader(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); diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/heartbeat/SubscriptionDataTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/heartbeat/SubscriptionDataTest.java similarity index 98% rename from common/src/test/java/org/apache/rocketmq/common/protocol/heartbeat/SubscriptionDataTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/heartbeat/SubscriptionDataTest.java index 533cd2f9965..a0dd665d911 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/heartbeat/SubscriptionDataTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/heartbeat/SubscriptionDataTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.heartbeat; +package org.apache.rocketmq.remoting.protocol.heartbeat; import org.apache.rocketmq.common.filter.ExpressionType; import org.apache.rocketmq.remoting.protocol.RemotingSerializable; diff --git a/common/src/test/java/org/apache/rocketmq/common/protocol/route/TopicRouteDataTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/route/TopicRouteDataTest.java similarity index 78% rename from common/src/test/java/org/apache/rocketmq/common/protocol/route/TopicRouteDataTest.java rename to remoting/src/test/java/org/apache/rocketmq/remoting/protocol/route/TopicRouteDataTest.java index 5c7c6d1d1ff..9bee83a26d2 100644 --- a/common/src/test/java/org/apache/rocketmq/common/protocol/route/TopicRouteDataTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/route/TopicRouteDataTest.java @@ -15,21 +15,16 @@ * limitations under the License. */ -package org.apache.rocketmq.common.protocol.route; +package org.apache.rocketmq.remoting.protocol.route; -import org.apache.rocketmq.common.protocol.route.BrokerData; -import org.apache.rocketmq.common.protocol.route.QueueData; -import org.apache.rocketmq.common.protocol.route.TopicRouteData; -import org.apache.rocketmq.remoting.protocol.RemotingSerializable; -import org.junit.Test; - 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; -import static org.assertj.core.api.Assertions.within; public class TopicRouteDataTest { @@ -45,10 +40,10 @@ public void testTopicRouteDataClone() throws Exception { queueData.setWriteQueueNums(8); queueData.setTopicSysFlag(0); - List queueDataList = new ArrayList(); + List queueDataList = new ArrayList<>(); queueDataList.add(queueData); - HashMap brokerAddrs = new HashMap(); + HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "192.168.0.47:10911"); brokerAddrs.put(1L, "192.168.0.47:10921"); @@ -57,14 +52,14 @@ public void testTopicRouteDataClone() throws Exception { brokerData.setBrokerName("broker-a"); brokerData.setCluster("TestCluster"); - List brokerDataList = new ArrayList(); + List brokerDataList = new ArrayList<>(); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); - topicRouteData.setFilterServerTable(new HashMap>()); + topicRouteData.setFilterServerTable(new HashMap<>()); topicRouteData.setQueueDatas(queueDataList); - assertThat(topicRouteData.cloneTopicRouteData()).isEqualTo(topicRouteData); + assertThat(new TopicRouteData(topicRouteData)).isEqualTo(topicRouteData); } @@ -80,10 +75,10 @@ public void testTopicRouteDataJsonSerialize() throws Exception { queueData.setWriteQueueNums(8); queueData.setTopicSysFlag(0); - List queueDataList = new ArrayList(); + List queueDataList = new ArrayList<>(); queueDataList.add(queueData); - HashMap brokerAddrs = new HashMap(); + HashMap brokerAddrs = new HashMap<>(); brokerAddrs.put(0L, "192.168.0.47:10911"); brokerAddrs.put(1L, "192.168.0.47:10921"); @@ -92,11 +87,11 @@ public void testTopicRouteDataJsonSerialize() throws Exception { brokerData.setBrokerName("broker-a"); brokerData.setCluster("TestCluster"); - List brokerDataList = new ArrayList(); + List brokerDataList = new ArrayList<>(); brokerDataList.add(brokerData); topicRouteData.setBrokerDatas(brokerDataList); - topicRouteData.setFilterServerTable(new HashMap>()); + topicRouteData.setFilterServerTable(new HashMap<>()); topicRouteData.setQueueDatas(queueDataList); String topicRouteDataJsonStr = RemotingSerializable.toJson(topicRouteData, true); diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingTest.java new file mode 100644 index 00000000000..6b8a1392f5b --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingTest.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.ImmutableList; +import java.util.Map; +import org.apache.rocketmq.remoting.protocol.RemotingSerializable; +import org.junit.Assert; +import org.junit.Test; + +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()); + Assert.assertEquals(1, ((JSONArray)((JSONObject) mappingDetailMap.get("hostedQueues")).get("0")).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)); + } + } + + @Test + public void test() { + + } +} diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingUtilsTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingUtilsTest.java new file mode 100644 index 00000000000..a12c9f89200 --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicQueueMappingUtilsTest.java @@ -0,0 +1,319 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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); + + } +} diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/CustomizedRetryPolicyTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/CustomizedRetryPolicyTest.java new file mode 100644 index 00000000000..eb215b32528 --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/CustomizedRetryPolicyTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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)); + } +} diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/ExponentialRetryPolicyTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/ExponentialRetryPolicyTest.java new file mode 100644 index 00000000000..0a0421be52d --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/ExponentialRetryPolicyTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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)); + } +} diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/GroupRetryPolicyTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/GroupRetryPolicyTest.java new file mode 100644 index 00000000000..c449e564f94 --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/subscription/GroupRetryPolicyTest.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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); + } +} diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/topic/OffsetMovedEventTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/topic/OffsetMovedEventTest.java new file mode 100644 index 00000000000..c8d4ef9d7b5 --- /dev/null +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/protocol/topic/OffsetMovedEventTest.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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; + } +} diff --git a/remoting/src/test/resources/rmq.logback-test.xml b/remoting/src/test/resources/rmq.logback-test.xml new file mode 100644 index 00000000000..8695d52d57c --- /dev/null +++ b/remoting/src/test/resources/rmq.logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/srvutil/BUILD.bazel b/srvutil/BUILD.bazel new file mode 100644 index 00000000000..a47a60cb160 --- /dev/null +++ b/srvutil/BUILD.bazel @@ -0,0 +1,60 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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", + "//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_fastjson", + "@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", + "//remoting", + "//: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", + ], +) diff --git a/srvutil/pom.xml b/srvutil/pom.xml index f49cd6fd131..c9cae87146f 100644 --- a/srvutil/pom.xml +++ b/srvutil/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.4-SNAPSHOT + 5.1.3 4.0.0 @@ -27,6 +27,9 @@ rocketmq-srvutil rocketmq-srvutil ${project.version} + + ${basedir}/.. + @@ -41,5 +44,13 @@ commons-cli commons-cli + + com.google.guava + guava + + + com.googlecode.concurrentlinkedhashmap + concurrentlinkedhashmap-lru + diff --git a/srvutil/src/main/java/org/apache/rocketmq/srvutil/AclFileWatchService.java b/srvutil/src/main/java/org/apache/rocketmq/srvutil/AclFileWatchService.java index e6fe5b3588f..eff9b422857 100644 --- a/srvutil/src/main/java/org/apache/rocketmq/srvutil/AclFileWatchService.java +++ b/srvutil/src/main/java/org/apache/rocketmq/srvutil/AclFileWatchService.java @@ -22,8 +22,8 @@ import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -34,7 +34,7 @@ import java.util.Map; public class AclFileWatchService extends ServiceThread { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); private final String aclPath; private int aclFilesNum; diff --git a/srvutil/src/main/java/org/apache/rocketmq/srvutil/FileWatchService.java b/srvutil/src/main/java/org/apache/rocketmq/srvutil/FileWatchService.java index 111cedea792..06c301bec9c 100644 --- a/srvutil/src/main/java/org/apache/rocketmq/srvutil/FileWatchService.java +++ b/srvutil/src/main/java/org/apache/rocketmq/srvutil/FileWatchService.java @@ -17,41 +17,35 @@ 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.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import org.apache.commons.lang3.StringUtils; -import org.apache.rocketmq.common.ServiceThread; +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.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; -public class FileWatchService extends ServiceThread { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); +public class FileWatchService extends LifecycleAwareServiceThread { + private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME); - private final List watchFiles; - private final List fileCurrentHash; + private final Map currentHash = new HashMap<>(); private final Listener listener; private static final int WATCH_INTERVAL = 500; - private MessageDigest md = MessageDigest.getInstance("MD5"); + private final MessageDigest md = MessageDigest.getInstance("MD5"); public FileWatchService(final String[] watchFiles, final Listener listener) throws Exception { this.listener = listener; - this.watchFiles = new ArrayList<>(); - this.fileCurrentHash = new ArrayList<>(); - - for (int i = 0; i < watchFiles.length; i++) { - if (StringUtils.isNotEmpty(watchFiles[i]) && new File(watchFiles[i]).exists()) { - this.watchFiles.add(watchFiles[i]); - this.fileCurrentHash.add(hash(watchFiles[i])); + for (String file : watchFiles) { + if (!Strings.isNullOrEmpty(file) && new File(file).exists()) { + currentHash.put(file, md5Digest(file)); } } } @@ -62,36 +56,52 @@ public String getServiceName() { } @Override - public void run() { + public void run0() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { this.waitForRunning(WATCH_INTERVAL); - - for (int i = 0; i < watchFiles.size(); i++) { - String newHash; - try { - newHash = hash(watchFiles.get(i)); - } catch (Exception ignored) { - log.warn(this.getServiceName() + " service has exception when calculate the file hash. ", ignored); - continue; - } - if (!newHash.equals(fileCurrentHash.get(i))) { - fileCurrentHash.set(i, newHash); - listener.onChanged(watchFiles.get(i)); + for (Map.Entry entry : currentHash.entrySet()) { + String newHash = md5Digest(entry.getKey()); + if (!newHash.equals(currentHash.get(entry.getKey()))) { + entry.setValue(newHash); + listener.onChanged(entry.getKey()); } } } catch (Exception e) { - log.warn(this.getServiceName() + " service has exception. ", e); + log.warn(this.getServiceName() + " service raised an unexpected exception.", e); } } log.info(this.getServiceName() + " service end"); } - private String hash(String filePath) throws IOException, NoSuchAlgorithmException { + /** + * 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); - md.update(Files.readAllBytes(path)); + 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); } @@ -99,6 +109,7 @@ private String hash(String filePath) throws IOException, NoSuchAlgorithmExceptio public interface Listener { /** * Will be called when the target files are changed + * * @param path the changed file path */ void onChanged(String path); diff --git a/srvutil/src/main/java/org/apache/rocketmq/srvutil/ShutdownHookThread.java b/srvutil/src/main/java/org/apache/rocketmq/srvutil/ShutdownHookThread.java index ba01e1ebae2..15b57bf0c97 100644 --- a/srvutil/src/main/java/org/apache/rocketmq/srvutil/ShutdownHookThread.java +++ b/srvutil/src/main/java/org/apache/rocketmq/srvutil/ShutdownHookThread.java @@ -17,7 +17,7 @@ package org.apache.rocketmq.srvutil; -import org.apache.rocketmq.logging.InternalLogger; +import org.apache.rocketmq.logging.org.slf4j.Logger; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; @@ -29,7 +29,7 @@ public class ShutdownHookThread extends Thread { private volatile boolean hasShutdown = false; private AtomicInteger shutdownTimes = new AtomicInteger(0); - private final InternalLogger log; + private final Logger log; private final Callable callback; /** @@ -38,7 +38,7 @@ public class ShutdownHookThread extends Thread { * @param log The log instance is used in hook thread. * @param callback The call back function. */ - public ShutdownHookThread(InternalLogger log, Callable callback) { + public ShutdownHookThread(Logger log, Callable callback) { super("ShutdownHook"); this.log = log; this.callback = callback; diff --git a/srvutil/src/main/test/org/apache/rocketmq/srvutil/FileWatchServiceTest.java b/srvutil/src/test/java/org/apache/rocketmq/srvutil/FileWatchServiceTest.java similarity index 79% rename from srvutil/src/main/test/org/apache/rocketmq/srvutil/FileWatchServiceTest.java rename to srvutil/src/test/java/org/apache/rocketmq/srvutil/FileWatchServiceTest.java index 791abcf0c95..aeae6253141 100644 --- a/srvutil/src/main/test/org/apache/rocketmq/srvutil/FileWatchServiceTest.java +++ b/srvutil/src/test/java/org/apache/rocketmq/srvutil/FileWatchServiceTest.java @@ -20,6 +20,8 @@ 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; @@ -39,17 +41,16 @@ public class FileWatchServiceTest { public void watchSingleFile() throws Exception { final File file = tempFolder.newFile(); final Semaphore waitSemaphore = new Semaphore(0); - FileWatchService fileWatchService = new FileWatchService(new String[] {file.getAbsolutePath()}, new FileWatchService.Listener() { - @Override - public void onChanged(String path) { - assertThat(file.getAbsolutePath()).isEqualTo(path); - waitSemaphore.release(); - } + 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 @@ -57,46 +58,42 @@ public void watchSingleFile_FileDeleted() throws Exception { File file = tempFolder.newFile(); final Semaphore waitSemaphore = new Semaphore(0); FileWatchService fileWatchService = new FileWatchService(new String[] {file.getAbsolutePath()}, - new FileWatchService.Listener() { - @Override - public void onChanged(String path) { - waitSemaphore.release(); - } - }); + path -> waitSemaphore.release()); fileWatchService.start(); - file.delete(); + fileWatchService.awaitStarted(1000); + assertThat(file.delete()).isTrue(); boolean result = waitSemaphore.tryAcquire(1, 1000, TimeUnit.MILLISECONDS); assertThat(result).isFalse(); - file.createNewFile(); + 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()}, - new FileWatchService.Listener() { - @Override - public void onChanged(String path) { - waitSemaphore.release(); - } - }); + path -> waitSemaphore.release()); fileWatchService.start(); - fileA.delete(); + 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(); - fileA.createNewFile(); + assertThat(fileA.createNewFile()).isTrue(); modifyFile(fileA); result = waitSemaphore.tryAcquire(1, 1000, TimeUnit.MILLISECONDS); assertThat(result).isTrue(); + fileWatchService.shutdown(); } @Test @@ -106,17 +103,16 @@ public void watchTwoFiles_ModifyOne() throws Exception { final Semaphore waitSemaphore = new Semaphore(0); FileWatchService fileWatchService = new FileWatchService( new String[] {fileA.getAbsolutePath(), fileB.getAbsolutePath()}, - new FileWatchService.Listener() { - @Override - public void onChanged(String path) { + path -> { assertThat(path).isEqualTo(fileA.getAbsolutePath()); waitSemaphore.release(); - } - }); + }); fileWatchService.start(); + fileWatchService.awaitStarted(1000); modifyFile(fileA); - boolean result = waitSemaphore.tryAcquire(1, 1000, TimeUnit.MILLISECONDS); + boolean result = waitSemaphore.tryAcquire(1, 2000, TimeUnit.MILLISECONDS); assertThat(result).isTrue(); + fileWatchService.shutdown(); } @Test @@ -126,13 +122,9 @@ public void watchTwoFiles() throws Exception { final Semaphore waitSemaphore = new Semaphore(0); FileWatchService fileWatchService = new FileWatchService( new String[] {fileA.getAbsolutePath(), fileB.getAbsolutePath()}, - new FileWatchService.Listener() { - @Override - public void onChanged(String path) { - waitSemaphore.release(); - } - }); + path -> waitSemaphore.release()); fileWatchService.start(); + fileWatchService.awaitStarted(1000); modifyFile(fileA); modifyFile(fileB); boolean result = waitSemaphore.tryAcquire(2, 1000, TimeUnit.MILLISECONDS); diff --git a/srvutil/src/test/resources/rmq.logback-test.xml b/srvutil/src/test/resources/rmq.logback-test.xml new file mode 100644 index 00000000000..8695d52d57c --- /dev/null +++ b/srvutil/src/test/resources/rmq.logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/store/BUILD.bazel b/store/BUILD.bazel new file mode 100644 index 00000000000..ba2cd4a02c8 --- /dev/null +++ b/store/BUILD.bazel @@ -0,0 +1,83 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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_fastjson", + "@maven//:com_conversantmedia_disruptor", + "@maven//:com_google_guava_guava", + "@maven//:commons_collections_commons_collections", + "@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", + ], +) + +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_fastjson", + "@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", + ], +) + +GenTestRules( + name = "GeneratedTestRules", + exclude_tests = [ + # This test is extremely slow and flaky, exclude it. + "src/test/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHATest", + ], + 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", + ], + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], +) diff --git a/store/pom.xml b/store/pom.xml index c583057fd6e..0712140c1df 100644 --- a/store/pom.xml +++ b/store/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.4-SNAPSHOT + 5.1.3 4.0.0 @@ -27,34 +27,45 @@ rocketmq-store rocketmq-store ${project.version} + + ${basedir}/.. + + io.openmessaging.storage dledger - 0.2.3 org.apache.rocketmq rocketmq-remoting - - org.slf4j - slf4j-log4j12 - ${project.groupId} - rocketmq-common + rocketmq-remoting net.java.dev.jna jna - ch.qos.logback - logback-classic - test + com.conversantmedia + disruptor + + + com.google.guava + guava + + + + org.slf4j + slf4j-api + + + io.github.aliyunmq + rocketmq-shaded-slf4j-api-bridge diff --git a/store/src/main/java/org/apache/rocketmq/store/AllocateMappedFileService.java b/store/src/main/java/org/apache/rocketmq/store/AllocateMappedFileService.java index acb1d54dc67..dca7d53258d 100644 --- a/store/src/main/java/org/apache/rocketmq/store/AllocateMappedFileService.java +++ b/store/src/main/java/org/apache/rocketmq/store/AllocateMappedFileService.java @@ -27,20 +27,22 @@ import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static int waitTimeOut = 1000 * 5; private ConcurrentMap requestTable = - new ConcurrentHashMap(); + new ConcurrentHashMap<>(); private PriorityBlockingQueue requestQueue = - new PriorityBlockingQueue(); + new PriorityBlockingQueue<>(); private volatile boolean hasException = false; private DefaultMessageStore messageStore; @@ -50,7 +52,7 @@ public AllocateMappedFileService(DefaultMessageStore messageStore) { public MappedFile putRequestAndReturnMappedFile(String nextFilePath, String nextNextFilePath, int fileSize) { int canSubmitRequests = 2; - if (this.messageStore.getMessageStoreConfig().isTransientStorePoolEnable()) { + 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.getTransientStorePool().availableBufferNums() - this.requestQueue.size(); @@ -97,7 +99,9 @@ public MappedFile putRequestAndReturnMappedFile(String nextFilePath, String next 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; @@ -117,6 +121,9 @@ public MappedFile putRequestAndReturnMappedFile(String nextFilePath, String next @Override public String getServiceName() { + if (messageStore != null && messageStore.getBrokerConfig().isInBrokerContainer()) { + return messageStore.getBrokerIdentity().getIdentifier() + AllocateMappedFileService.class.getSimpleName(); + } return AllocateMappedFileService.class.getSimpleName(); } @@ -164,16 +171,16 @@ private boolean mmapOperation() { long beginTime = System.currentTimeMillis(); MappedFile mappedFile; - if (messageStore.getMessageStoreConfig().isTransientStorePoolEnable()) { + if (messageStore.isTransientStorePoolEnable()) { try { mappedFile = ServiceLoader.load(MappedFile.class).iterator().next(); mappedFile.init(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool()); } catch (RuntimeException e) { log.warn("Use default implementation."); - mappedFile = new MappedFile(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool()); + mappedFile = new DefaultMappedFile(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool()); } } else { - mappedFile = new MappedFile(req.getFilePath(), req.getFileSize()); + mappedFile = new DefaultMappedFile(req.getFilePath(), req.getFileSize()); } long elapsedTime = UtilAll.computeElapsedTimeMilliseconds(beginTime); diff --git a/store/src/main/java/org/apache/rocketmq/store/AppendMessageCallback.java b/store/src/main/java/org/apache/rocketmq/store/AppendMessageCallback.java index 5499c900660..1cbccdf8776 100644 --- a/store/src/main/java/org/apache/rocketmq/store/AppendMessageCallback.java +++ b/store/src/main/java/org/apache/rocketmq/store/AppendMessageCallback.java @@ -18,7 +18,7 @@ import java.nio.ByteBuffer; import org.apache.rocketmq.common.message.MessageExtBatch; -import org.apache.rocketmq.store.CommitLog.PutMessageContext; +import org.apache.rocketmq.common.message.MessageExtBrokerInner; /** * Write messages callback interface @@ -26,7 +26,7 @@ public interface AppendMessageCallback { /** - * After message serialization, write MapedByteBuffer + * After message serialization, write MappedByteBuffer * * @return How many bytes to write */ @@ -34,7 +34,7 @@ AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer byteBuf final int maxBlank, final MessageExtBrokerInner msg, PutMessageContext putMessageContext); /** - * After batched message serialization, write MapedByteBuffer + * After batched message serialization, write MappedByteBuffer * * @param messageExtBatch, backed up by a byte array * @return How many bytes to write diff --git a/store/src/main/java/org/apache/rocketmq/store/AppendMessageResult.java b/store/src/main/java/org/apache/rocketmq/store/AppendMessageResult.java index de3c03b307f..98bf203ad5d 100644 --- a/store/src/main/java/org/apache/rocketmq/store/AppendMessageResult.java +++ b/store/src/main/java/org/apache/rocketmq/store/AppendMessageResult.java @@ -54,6 +54,13 @@ public AppendMessageResult(AppendMessageStatus status, long wroteOffset, int wro 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; @@ -65,6 +72,18 @@ public AppendMessageResult(AppendMessageStatus status, long wroteOffset, int wro 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; } diff --git a/store/src/main/java/org/apache/rocketmq/store/CommitLog.java b/store/src/main/java/org/apache/rocketmq/store/CommitLog.java index bf66af500aa..5a5c90c5a0f 100644 --- a/store/src/main/java/org/apache/rocketmq/store/CommitLog.java +++ b/store/src/main/java/org/apache/rocketmq/store/CommitLog.java @@ -16,105 +16,115 @@ */ package org.apache.rocketmq.store; +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 com.sun.jna.NativeLong; +import com.sun.jna.Pointer; +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.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.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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.ha.HAService; -import org.apache.rocketmq.store.schedule.ScheduleMessageService; - -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.nio.ByteBuffer; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -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.function.Supplier; +import org.apache.rocketmq.store.ha.autoswitch.AutoSwitchHAService; +import org.apache.rocketmq.store.logfile.MappedFile; +import org.apache.rocketmq.store.util.LibC; +import sun.nio.ch.DirectBuffer; /** * Store all metadata downtime for recovery, data protection reliability */ -public class CommitLog { +public class CommitLog implements Swappable { // Message's MAGIC CODE daa320a7 public final static int MESSAGE_MAGIC_CODE = -626843481; - protected static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + protected static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); // End of file empty MAGIC CODE cbd43194 - protected final static int BLANK_MAGIC_CODE = -875286124; + public final static int BLANK_MAGIC_CODE = -875286124; protected final MappedFileQueue mappedFileQueue; protected final DefaultMessageStore defaultMessageStore; - private final FlushCommitLogService flushCommitLogService; - //If TransientStorePool enabled, we must flush message to FileChannel at fixed periods - private final FlushCommitLogService commitLogService; + private final FlushManager flushManager; + private final ColdDataCheckService coldDataCheckService; private final AppendMessageCallback appendMessageCallback; private final ThreadLocal putMessageThreadLocal; - protected HashMap topicQueueTable = new HashMap(1024); - protected Map lmqTopicQueueTable = new ConcurrentHashMap<>(1024); + 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 MultiDispatch multiDispatch; private final FlushDiskWatcher flushDiskWatcher; - public CommitLog(final DefaultMessageStore defaultMessageStore) { - String storePath = defaultMessageStore.getMessageStoreConfig().getStorePathCommitLog(); - if (storePath.contains(MessageStoreConfig.MULTI_PATH_SPLITTER)) { - this.mappedFileQueue = new MultiPathMappedFileQueue(defaultMessageStore.getMessageStoreConfig(), - defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(), - defaultMessageStore.getAllocateMappedFileService(), this::getFullStorePaths); + protected int commitLogSize; + + public CommitLog(final DefaultMessageStore messageStore) { + String storePath = messageStore.getMessageStoreConfig().getStorePathCommitLog(); + if (storePath.contains(MixAll.MULTI_PATH_SPLITTER)) { + this.mappedFileQueue = new MultiPathMappedFileQueue(messageStore.getMessageStoreConfig(), + messageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(), + messageStore.getAllocateMappedFileService(), this::getFullStorePaths); } else { this.mappedFileQueue = new MappedFileQueue(storePath, - defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(), - defaultMessageStore.getAllocateMappedFileService()); + messageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(), + messageStore.getAllocateMappedFileService()); } - this.defaultMessageStore = defaultMessageStore; - - if (FlushDiskType.SYNC_FLUSH == defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) { - this.flushCommitLogService = new GroupCommitService(); - } else { - this.flushCommitLogService = new FlushRealTimeService(); - } + this.defaultMessageStore = messageStore; - this.commitLogService = new CommitRealTimeService(); + this.flushManager = new DefaultFlushManager(); + this.coldDataCheckService = new ColdDataCheckService(); - this.appendMessageCallback = new DefaultAppendMessageCallback(defaultMessageStore.getMessageStoreConfig().getMaxMessageSize()); + this.appendMessageCallback = new DefaultAppendMessageCallback(); putMessageThreadLocal = new ThreadLocal() { @Override protected PutMessageThreadLocal initialValue() { return new PutMessageThreadLocal(defaultMessageStore.getMessageStoreConfig().getMaxMessageSize()); } }; - this.putMessageLock = defaultMessageStore.getMessageStoreConfig().isUseReentrantLockWhenPutMessage() ? new PutMessageReentrantLock() : new PutMessageSpinLock(); + this.putMessageLock = messageStore.getMessageStoreConfig().isUseReentrantLockWhenPutMessage() ? new PutMessageReentrantLock() : new PutMessageSpinLock(); + + this.flushDiskWatcher = new FlushDiskWatcher(); - this.multiDispatch = new MultiDispatch(defaultMessageStore, this); + this.topicQueueLock = new TopicQueueLock(); - flushDiskWatcher = new FlushDiskWatcher(); + this.commitLogSize = messageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); } public void setFullStorePaths(Set fullStorePaths) { @@ -125,36 +135,41 @@ 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 start() { - this.flushCommitLogService.start(); - + this.flushManager.start(); + log.info("start commitLog successfully. storeRoot: {}", this.defaultMessageStore.getMessageStoreConfig().getStorePathRootDir()); flushDiskWatcher.setDaemon(true); flushDiskWatcher.start(); - - - if (defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) { - this.commitLogService.start(); + if (this.coldDataCheckService != null) { + this.coldDataCheckService.start(); } } public void shutdown() { - if (defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) { - this.commitLogService.shutdown(); - } - - this.flushCommitLogService.shutdown(); - + this.flushManager.shutdown(); + log.info("shutdown commitLog successfully. storeRoot: {}", this.defaultMessageStore.getMessageStoreConfig().getStorePathRootDir()); flushDiskWatcher.shutdown(true); + if (this.coldDataCheckService != null) { + this.coldDataCheckService.shutdown(); + } } public long flush() { @@ -163,6 +178,10 @@ public long flush() { return this.mappedFileQueue.getFlushedWhere(); } + public long getFlushedWhere() { + return this.mappedFileQueue.getFlushedWhere(); + } + public long getMaxOffset() { return this.mappedFileQueue.getMaxOffset(); } @@ -181,7 +200,17 @@ public int deleteExpiredFile( final long intervalForcibly, final boolean cleanImmediately ) { - return this.mappedFileQueue.deleteExpiredFileByTime(expiredTime, deleteFilesInterval, intervalForcibly, 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); } /** @@ -203,33 +232,106 @@ public SelectMappedBufferResult getData(final long offset, final boolean returnF 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 */ public void recoverNormally(long maxPhyOffsetOfConsumeQueue) { boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover(); + boolean checkDupInfo = this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable(); final List mappedFiles = this.mappedFileQueue.getMappedFiles(); if (!mappedFiles.isEmpty()) { // Began to recover from the last third file int index = mappedFiles.size() - 3; - if (index < 0) + if (index < 0) { index = 0; + } MappedFile mappedFile = mappedFiles.get(index); ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); long processOffset = mappedFile.getFileFromOffset(); long mappedFileOffset = 0; + long lastValidMsgPhyOffset = this.getConfirmOffset(); + // normal recover doesn't require dispatching + boolean doDispatch = false; while (true) { - DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover); + DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover, checkDupInfo); int size = dispatchRequest.getMsgSize(); // 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 @@ -245,12 +347,28 @@ else if (dispatchRequest.isSuccess() && size == 0) { } // 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); + } + this.mappedFileQueue.setFlushedWhere(processOffset); this.mappedFileQueue.setCommittedWhere(processOffset); this.mappedFileQueue.truncateDirtyFiles(processOffset); @@ -269,8 +387,9 @@ else if (!dispatchRequest.isSuccess()) { } } - public DispatchRequest checkMessageAndReturnSize(java.nio.ByteBuffer byteBuffer, final boolean checkCRC) { - return this.checkMessageAndReturnSize(byteBuffer, checkCRC, 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) { @@ -285,7 +404,7 @@ private void doNothingForDeadCode(final Object obj) { * @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 readBody) { + final boolean checkDupInfo, final boolean readBody) { try { // 1 TOTAL SIZE int totalSize = byteBuffer.getInt(); @@ -293,7 +412,8 @@ public DispatchRequest checkMessageAndReturnSize(java.nio.ByteBuffer byteBuffer, // 2 MAGIC CODE int magicCode = byteBuffer.getInt(); switch (magicCode) { - case MESSAGE_MAGIC_CODE: + case MessageDecoder.MESSAGE_MAGIC_CODE: + case MessageDecoder.MESSAGE_MAGIC_CODE_V2: break; case BLANK_MAGIC_CODE: return new DispatchRequest(0, true /* success */); @@ -302,6 +422,8 @@ public DispatchRequest checkMessageAndReturnSize(java.nio.ByteBuffer byteBuffer, return new DispatchRequest(-1, false /* success */); } + MessageVersion messageVersion = MessageVersion.valueOfMagicCode(magicCode); + byte[] bytesContent = new byte[totalSize]; int bodyCRC = byteBuffer.getInt(); @@ -355,7 +477,7 @@ public DispatchRequest checkMessageAndReturnSize(java.nio.ByteBuffer byteBuffer, } } - byte topicLen = byteBuffer.get(); + int topicLen = messageVersion.getTopicLength(byteBuffer); byteBuffer.get(bytesContent, 0, topicLen); String topic = new String(bytesContent, 0, topicLen, MessageDecoder.CHARSET_UTF8); @@ -374,6 +496,14 @@ public DispatchRequest checkMessageAndReturnSize(java.nio.ByteBuffer byteBuffer, 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 (tags != null && tags.length() > 0) { tagsCode = MessageExtBrokerInner.tagsString2tagsCode(MessageExt.parseTopicFilterType(sysFlag), tags); @@ -385,19 +515,19 @@ public DispatchRequest checkMessageAndReturnSize(java.nio.ByteBuffer byteBuffer, if (TopicValidator.RMQ_SYS_SCHEDULE_TOPIC.equals(topic) && t != null) { int delayLevel = Integer.parseInt(t); - if (delayLevel > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) { - delayLevel = this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel(); + if (delayLevel > this.defaultMessageStore.getMaxDelayLevel()) { + delayLevel = this.defaultMessageStore.getMaxDelayLevel(); } if (delayLevel > 0) { - tagsCode = this.defaultMessageStore.getScheduleMessageService().computeDeliverTimestamp(delayLevel, + tagsCode = this.defaultMessageStore.computeDeliverTimestamp(delayLevel, storeTimestamp); } } } } - int readLength = calMsgLength(sysFlag, bodyLen, topicLen, propertiesLength); + int readLength = MessageExtEncoder.calMsgLength(messageVersion, sysFlag, bodyLen, topicLen, propertiesLength); if (totalSize != readLength) { doNothingForDeadCode(reconsumeTimes); doNothingForDeadCode(flag); @@ -410,7 +540,7 @@ public DispatchRequest checkMessageAndReturnSize(java.nio.ByteBuffer byteBuffer, return new DispatchRequest(totalSize, false/* success */); } - return new DispatchRequest( + DispatchRequest dispatchRequest = new DispatchRequest( topic, queueId, physicOffset, @@ -424,48 +554,83 @@ public DispatchRequest checkMessageAndReturnSize(java.nio.ByteBuffer byteBuffer, preparedTransactionOffset, propertiesMap ); + + setBatchSizeIfNeeded(propertiesMap, dispatchRequest); + + return dispatchRequest; } catch (Exception e) { } return new DispatchRequest(-1, false /* success */); } - protected static int calMsgLength(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; - final int msgLen = 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 + (bodyLength > 0 ? bodyLength : 0) //BODY - + 1 + topicLength //TOPIC - + 2 + (propertiesLength > 0 ? propertiesLength : 0) //propertiesLength - + 0; - return msgLen; + 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() { - return this.confirmOffset; + 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(); + } + // 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 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; } @Deprecated public void recoverAbnormally(long maxPhyOffsetOfConsumeQueue) { // recover by the minimum time stamp boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover(); + boolean checkDupInfo = this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable(); final List mappedFiles = this.mappedFileQueue.getMappedFiles(); if (!mappedFiles.isEmpty()) { // Looking beginning to recover from which file @@ -487,27 +652,34 @@ public void recoverAbnormally(long maxPhyOffsetOfConsumeQueue) { ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); long processOffset = mappedFile.getFileFromOffset(); long mappedFileOffset = 0; + long lastValidMsgPhyOffset = processOffset; + long lastConfirmValidMsgPhyOffset = processOffset; + // abnormal recover require dispatching + boolean doDispatch = true; while (true) { - DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover); + DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover, checkDupInfo); int size = dispatchRequest.getMsgSize(); if (dispatchRequest.isSuccess()) { // Normal data if (size > 0) { + lastValidMsgPhyOffset = processOffset + mappedFileOffset; mappedFileOffset += size; - if (this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) { - if (dispatchRequest.getCommitLogOffset() < this.defaultMessageStore.getConfirmOffset()) { - this.defaultMessageStore.doDispatch(dispatchRequest); + 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.defaultMessageStore.doDispatch(dispatchRequest); + 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 @@ -523,12 +695,28 @@ else if (size == 0) { } } } else { + + if (size > 0) { + log.warn("found a half message at {}, it will be truncated.", processOffset + mappedFileOffset); + } + log.info("recover physics file end, " + mappedFile.getFileName() + " pos=" + byteBuffer.position()); 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() > lastConfirmValidMsgPhyOffset) { + log.error("confirmOffset {} is larger than lastConfirmValidMsgPhyOffset {}, correct confirmOffset to lastConfirmValidMsgPhyOffset", this.defaultMessageStore.getConfirmOffset(), lastConfirmValidMsgPhyOffset); + this.defaultMessageStore.setConfirmOffset(lastConfirmValidMsgPhyOffset); + } + } else { + this.setConfirmOffset(lastValidMsgPhyOffset); + } this.mappedFileQueue.setFlushedWhere(processOffset); this.mappedFileQueue.setCommittedWhere(processOffset); this.mappedFileQueue.truncateDirtyFiles(processOffset); @@ -548,11 +736,30 @@ else if (size == 0) { } } + 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) { ByteBuffer byteBuffer = mappedFile.sliceByteBuffer(); - int magicCode = byteBuffer.getInt(MessageDecoder.MESSAGE_MAGIC_CODE_POSTION); - if (magicCode != MESSAGE_MAGIC_CODE) { + int magicCode = byteBuffer.getInt(MessageDecoder.MESSAGE_MAGIC_CODE_POSITION); + if (magicCode != MessageDecoder.MESSAGE_MAGIC_CODE && magicCode != MessageDecoder.MESSAGE_MAGIC_CODE_V2) { return false; } @@ -584,10 +791,6 @@ private boolean isMappedFileMatchedRecover(final MappedFile mappedFile) { return false; } - private void notifyMessageArriving() { - - } - public boolean resetOffset(long offset) { return this.mappedFileQueue.resetOffset(offset); } @@ -596,7 +799,7 @@ public long getBeginTimeInLock() { return beginTimeInLock; } - private String generateKey(StringBuilder keyBuilder, MessageExt messageExt) { + public String generateKey(StringBuilder keyBuilder, MessageExt messageExt) { keyBuilder.setLength(0); keyBuilder.append(messageExt.getTopic()); keyBuilder.append('-'); @@ -604,11 +807,27 @@ private String generateKey(StringBuilder keyBuilder, MessageExt messageExt) { 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 - msg.setStoreTimestamp(System.currentTimeMillis()); - // Set the message body BODY CRC (consider the most appropriate setting - // on the client) + 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())); // Back to Results AppendMessageResult result = null; @@ -616,27 +835,11 @@ public CompletableFuture asyncPutMessage(final MessageExtBroke StoreStatsService storeStatsService = this.defaultMessageStore.getStoreStatsService(); String topic = msg.getTopic(); -// int queueId msg.getQueueId(); - final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag()); - if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE - || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) { - // Delay Delivery - if (msg.getDelayTimeLevel() > 0) { - if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) { - msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()); - } - - topic = TopicValidator.RMQ_SYS_SCHEDULE_TOPIC; - int queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel()); - - // 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(topic); - msg.setQueueId(queueId); - } + 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(); @@ -650,62 +853,129 @@ public CompletableFuture asyncPutMessage(final MessageExtBroke } PutMessageThreadLocal putMessageThreadLocal = this.putMessageThreadLocal.get(); - PutMessageResult encodeResult = putMessageThreadLocal.getEncoder().encode(msg); - if (encodeResult != null) { - return CompletableFuture.completedFuture(encodeResult); - } - msg.setEncodedBuff(putMessageThreadLocal.getEncoder().encoderBuffer); - PutMessageContext putMessageContext = new PutMessageContext(generateKey(putMessageThreadLocal.getKeyBuilder(), msg)); - + updateMaxMessageSize(putMessageThreadLocal); + String topicQueueKey = generateKey(putMessageThreadLocal.getKeyBuilder(), msg); long elapsedTimeInLock = 0; MappedFile unlockMappedFile = null; + MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); - putMessageLock.lock(); //spin or ReentrantLock ,depending on store config - try { - MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); - long beginLockTimestamp = this.defaultMessageStore.getSystemClock().now(); - this.beginTimeInLock = beginLockTimestamp; + long currOffset; + if (mappedFile == null) { + currOffset = 0; + } else { + currOffset = mappedFile.getFileFromOffset() + mappedFile.getWrotePosition(); + } - // Here settings are stored timestamp, in order to ensure an orderly - // global - msg.setStoreTimestamp(beginLockTimestamp); + int needAckNums = this.defaultMessageStore.getMessageStoreConfig().getInSyncReplicas(); + boolean needHandleHA = needHandleHA(msg); - if (null == mappedFile || mappedFile.isFull()) { - mappedFile = this.mappedFileQueue.getLastMappedFile(0); // Mark: NewFile may be cause noise + 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 (null == mappedFile) { - log.error("create mapped file1 error, topic: " + msg.getTopic() + " clientAddr: " + msg.getBornHostString()); - return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, 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)); + } + } - result = mappedFile.appendMessage(msg, 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: " + msg.getTopic() + " clientAddr: " + msg.getBornHostString()); - return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, result)); - } - result = mappedFile.appendMessage(msg, this.appendMessageCallback, putMessageContext); - break; - case MESSAGE_SIZE_EXCEEDED: - case PROPERTIES_SIZE_EXCEEDED: - return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, result)); - case UNKNOWN_ERROR: - return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result)); - default: - return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result)); + 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: " + msg.getTopic() + " clientAddr: " + msg.getBornHostString()); + beginTimeInLock = 0; + return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPPED_FILE_FAILED, null)); + } - elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginLockTimestamp; + 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: " + msg.getTopic() + " clientAddr: " + msg.getBornHostString()); + beginTimeInLock = 0; + 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: + beginTimeInLock = 0; + return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, result)); + case UNKNOWN_ERROR: + beginTimeInLock = 0; + return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result)); + default: + beginTimeInLock = 0; + return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result)); + } + + elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginLockTimestamp; + beginTimeInLock = 0; + } finally { + putMessageLock.unlock(); + } + // Increase queue offset when messages are successfully written + if (AppendMessageStatus.PUT_OK.equals(result.getStatus())) { + this.defaultMessageStore.increaseOffset(msg, getMessageNum(msg)); + } } finally { - beginTimeInLock = 0; - putMessageLock.unlock(); + topicQueueLock.unlock(topicQueueKey); } if (elapsedTimeInLock > 500) { @@ -719,20 +989,10 @@ public CompletableFuture asyncPutMessage(final MessageExtBroke PutMessageResult putMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, result); // Statistics - storeStatsService.getSinglePutMessageTopicTimesTotal(msg.getTopic()).add(1); + storeStatsService.getSinglePutMessageTopicTimesTotal(msg.getTopic()).add(result.getMsgNum()); storeStatsService.getSinglePutMessageTopicSizeTotal(topic).add(result.getWroteBytes()); - CompletableFuture flushResultFuture = submitFlushRequest(result, msg); - CompletableFuture replicaResultFuture = submitReplicaRequest(result, msg); - return flushResultFuture.thenCombine(replicaResultFuture, (flushStatus, replicaStatus) -> { - if (flushStatus != PutMessageStatus.PUT_OK) { - putMessageResult.setPutMessageStatus(flushStatus); - } - if (replicaStatus != PutMessageStatus.PUT_OK) { - putMessageResult.setPutMessageStatus(replicaStatus); - } - return putMessageResult; - }); + return handleDiskFlushAndHA(putMessageResult, msg, needAckNums, needHandleHA); } public CompletableFuture asyncPutMessages(final MessageExtBatch messageExtBatch) { @@ -764,57 +1024,117 @@ public CompletableFuture asyncPutMessages(final MessageExtBatc 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(); - PutMessageContext putMessageContext = new PutMessageContext(generateKey(pmThreadLocal.getKeyBuilder(), messageExtBatch)); + String topicQueueKey = generateKey(pmThreadLocal.getKeyBuilder(), messageExtBatch); + + PutMessageContext putMessageContext = new PutMessageContext(topicQueueKey); messageExtBatch.setEncodedBuff(batchEncoder.encode(messageExtBatch, putMessageContext)); - putMessageLock.lock(); + topicQueueLock.lock(topicQueueKey); try { - long beginLockTimestamp = this.defaultMessageStore.getSystemClock().now(); - this.beginTimeInLock = beginLockTimestamp; + defaultMessageStore.assignOffset(messageExtBatch); - // Here settings are stored timestamp, in order to ensure an orderly - // global - messageExtBatch.setStoreTimestamp(beginLockTimestamp); + putMessageLock.lock(); + try { + long beginLockTimestamp = this.defaultMessageStore.getSystemClock().now(); + this.beginTimeInLock = beginLockTimestamp; - if (null == mappedFile || mappedFile.isFull()) { - mappedFile = this.mappedFileQueue.getLastMappedFile(0); // Mark: NewFile may be cause noise - } - if (null == mappedFile) { - log.error("Create mapped file1 error, topic: {} clientAddr: {}", messageExtBatch.getTopic(), messageExtBatch.getBornHostString()); - return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, null)); - } + // Here settings are stored timestamp, in order to ensure an orderly + // global + messageExtBatch.setStoreTimestamp(beginLockTimestamp); - 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_MAPEDFILE_FAILED, result)); + if (null == mappedFile || mappedFile.isFull()) { + mappedFile = this.mappedFileQueue.getLastMappedFile(0); // Mark: NewFile may be cause noise + 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)); + } + if (null == mappedFile) { + log.error("Create mapped file1 error, topic: {} clientAddr: {}", messageExtBatch.getTopic(), messageExtBatch.getBornHostString()); + beginTimeInLock = 0; + 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()); + beginTimeInLock = 0; + 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: + beginTimeInLock = 0; + return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, result)); + case UNKNOWN_ERROR: + default: + beginTimeInLock = 0; + return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result)); + } + + elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginLockTimestamp; + beginTimeInLock = 0; + } finally { + putMessageLock.unlock(); } - elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginLockTimestamp; + // Increase queue offset when messages are successfully written + if (AppendMessageStatus.PUT_OK.equals(result.getStatus())) { + this.defaultMessageStore.increaseOffset(messageExtBatch, (short) putMessageContext.getBatchSize()); + } } finally { - beginTimeInLock = 0; - putMessageLock.unlock(); + topicQueueLock.unlock(topicQueueKey); } if (elapsedTimeInLock > 500) { @@ -831,9 +1151,51 @@ public CompletableFuture asyncPutMessages(final MessageExtBatc storeStatsService.getSinglePutMessageTopicTimesTotal(messageExtBatch.getTopic()).add(result.getMsgNum()); storeStatsService.getSinglePutMessageTopicSizeTotal(messageExtBatch.getTopic()).add(result.getWroteBytes()); - CompletableFuture flushOKFuture = submitFlushRequest(result, messageExtBatch); - CompletableFuture replicaOKFuture = submitReplicaRequest(result, messageExtBatch); - return flushOKFuture.thenCombine(replicaOKFuture, (flushStatus, replicaStatus) -> { + 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); } @@ -842,59 +1204,34 @@ public CompletableFuture asyncPutMessages(final MessageExtBatc } return putMessageResult; }); + } + private CompletableFuture handleDiskFlush(AppendMessageResult result, MessageExt messageExt) { + return this.flushManager.handleDiskFlush(result, messageExt); } - public CompletableFuture submitFlushRequest(AppendMessageResult result, MessageExt messageExt) { - // Synchronization flush - if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) { - final GroupCommitService service = (GroupCommitService) this.flushCommitLogService; - if (messageExt.isWaitStoreMsgOK()) { - GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(), - 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 (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) { - flushCommitLogService.wakeup(); - } else { - commitLogService.wakeup(); - } + private CompletableFuture handleHA(AppendMessageResult result, PutMessageResult putMessageResult, + int needAckNums) { + if (needAckNums >= 0 && needAckNums <= 1) { return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK); } - } - public CompletableFuture submitReplicaRequest(AppendMessageResult result, MessageExt messageExt) { - if (BrokerRole.SYNC_MASTER == this.defaultMessageStore.getMessageStoreConfig().getBrokerRole()) { - HAService service = this.defaultMessageStore.getHaService(); - if (messageExt.isWaitStoreMsgOK()) { - if (service.isSlaveOK(result.getWroteBytes() + result.getWroteOffset())) { - GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(), - this.defaultMessageStore.getMessageStoreConfig().getSlaveTimeout()); - service.putRequest(request); - service.getWaitNotifyObject().wakeupAll(); - return request.future(); - } - else { - return CompletableFuture.completedFuture(PutMessageStatus.SLAVE_NOT_AVAILABLE); - } - } - } - 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 (offset >= this.getMinOffset()) { + if (offset >= this.getMinOffset() && offset + size <= this.getMaxOffset()) { SelectMappedBufferResult result = this.getMessage(offset, size); if (null != result) { try { @@ -929,7 +1266,11 @@ public SelectMappedBufferResult getMessage(final long offset, final int size) { MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(offset, offset == 0); if (mappedFile != null) { int pos = (int) (offset % mappedFileSize); - return mappedFile.selectMappedBuffer(pos, size); + SelectMappedBufferResult selectMappedBufferResult = mappedFile.selectMappedBuffer(pos, size); + if (null != selectMappedBufferResult) { + selectMappedBufferResult.setInCache(coldDataCheckService.isDataInPageCache(offset)); + return selectMappedBufferResult; + } } return null; } @@ -939,14 +1280,6 @@ public long rollNextFile(final long offset) { return offset + mappedFileSize - offset % mappedFileSize; } - public HashMap getTopicQueueTable() { - return topicQueueTable; - } - - public void setTopicQueueTable(HashMap topicQueueTable) { - this.topicQueueTable = topicQueueTable; - } - public void destroy() { this.mappedFileQueue.destroy(); } @@ -970,16 +1303,6 @@ public boolean retryDeleteFirstFile(final long intervalForcibly) { return this.mappedFileQueue.retryDeleteFirstFile(intervalForcibly); } - public void removeQueueFromTopicQueueTable(final String topic, final int queueId) { - String key = topic + "-" + queueId; - synchronized (this) { - this.topicQueueTable.remove(key); - this.lmqTopicQueueTable.remove(key); - } - - log.info("removeQueueFromTopicQueueTable OK Topic: {} QueueId: {}", topic, queueId); - } - public void checkSelf() { mappedFileQueue.checkSelf(); } @@ -998,8 +1321,24 @@ public long lockTimeMills() { return diff; } - public Map getLmqTopicQueueTable() { - return this.lmqTopicQueueTable; + 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 { @@ -1012,6 +1351,9 @@ class CommitRealTimeService extends FlushCommitLogService { @Override public String getServiceName() { + if (CommitLog.this.defaultMessageStore.getBrokerConfig().isInBrokerContainer()) { + return CommitLog.this.defaultMessageStore.getBrokerIdentity().getIdentifier() + CommitRealTimeService.class.getSimpleName(); + } return CommitRealTimeService.class.getSimpleName(); } @@ -1037,10 +1379,9 @@ public void run() { long end = System.currentTimeMillis(); if (!result) { this.lastCommitTimestamp = end; // result = false means some data committed. - //now wake up flush thread. - flushCommitLogService.wakeup(); + 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); } @@ -1063,6 +1404,7 @@ class FlushRealTimeService extends FlushCommitLogService { private long lastFlushTimestamp = 0; private long printTimes = 0; + @Override public void run() { CommitLog.log.info(this.getServiceName() + " service started"); @@ -1103,6 +1445,7 @@ public void run() { 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); } @@ -1126,6 +1469,9 @@ public void run() { @Override public String getServiceName() { + if (CommitLog.this.defaultMessageStore.getBrokerConfig().isInBrokerContainer()) { + return CommitLog.this.defaultMessageStore.getBrokerConfig().getIdentifier() + FlushRealTimeService.class.getSimpleName(); + } return FlushRealTimeService.class.getSimpleName(); } @@ -1135,14 +1481,16 @@ private void printFlushProgress() { } @Override - public long getJointime() { + public long getJoinTime() { return 1000 * 60 * 5; } } public static class GroupCommitRequest { private final long nextOffset; - private CompletableFuture flushOKFuture = new CompletableFuture<>(); + // 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) { @@ -1150,33 +1498,41 @@ public GroupCommitRequest(long nextOffset, long timeoutMillis) { this.deadLine = System.nanoTime() + (timeoutMillis * 1_000_000); } - public long getDeadLine() { - return deadLine; + public GroupCommitRequest(long nextOffset, long timeoutMillis, int ackNums) { + this(nextOffset, timeoutMillis); + this.ackNums = ackNums; } public long getNextOffset() { return nextOffset; } - public void wakeupCustomer(final PutMessageStatus putMessageStatus) { - this.flushOKFuture.complete(putMessageStatus); + 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 volatile LinkedList requestsWrite = new LinkedList(); - private volatile LinkedList requestsRead = new LinkedList(); + private volatile LinkedList requestsWrite = new LinkedList<>(); + private volatile LinkedList requestsRead = new LinkedList<>(); private final PutMessageSpinLock lock = new PutMessageSpinLock(); - public synchronized void putRequest(final GroupCommitRequest request) { + public void putRequest(final GroupCommitRequest request) { lock.lock(); try { this.requestsWrite.add(request); @@ -1224,6 +1580,7 @@ private void doCommit() { } } + @Override public void run() { CommitLog.log.info(this.getServiceName() + " service started"); @@ -1241,7 +1598,115 @@ public void run() { try { Thread.sleep(10); } catch (InterruptedException e) { - CommitLog.log.warn(this.getServiceName() + " Exception, ", e); + CommitLog.log.warn("GroupCommitService Exception, ", e); + } + + this.swapRequests(); + this.doCommit(); + + CommitLog.log.info(this.getServiceName() + " service end"); + } + + @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(this.getServiceName() + " service started"); + + while (!this.isStopped()) { + try { + this.waitForRunning(1); + this.doCommit(); + } catch (Exception e) { + CommitLog.log.warn(this.getServiceName() + " service has exception. ", 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) { @@ -1260,11 +1725,14 @@ protected void onWaitEnd() { @Override public String getServiceName() { - return GroupCommitService.class.getSimpleName(); + if (CommitLog.this.defaultMessageStore.getBrokerConfig().isInBrokerContainer()) { + return CommitLog.this.defaultMessageStore.getBrokerConfig().getIdentifier() + GroupCheckService.class.getSimpleName(); + } + return GroupCheckService.class.getSimpleName(); } @Override - public long getJointime() { + public long getJoinTime() { return 1000 * 60 * 5; } } @@ -1272,18 +1740,11 @@ public long getJointime() { 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; - private final ByteBuffer msgIdMemory; - private final ByteBuffer msgIdV6Memory; // Store the message content private final ByteBuffer msgStoreItemMemory; - // The maximum length of the message - private final int maxMessageSize; - DefaultAppendMessageCallback(final int size) { - this.msgIdMemory = ByteBuffer.allocate(4 + 4 + 8); - this.msgIdV6Memory = ByteBuffer.allocate(16 + 4 + 8); + DefaultAppendMessageCallback() { this.msgStoreItemMemory = ByteBuffer.allocate(END_FILE_MIN_BLANK_LENGTH); - this.maxMessageSize = size; } public AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer byteBuffer, final int maxBlank, @@ -1304,23 +1765,15 @@ public AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer }; // Record ConsumeQueue information - String key = putMessageContext.getTopicQueueTableKey(); - Long queueOffset = CommitLog.this.topicQueueTable.get(key); - if (null == queueOffset) { - queueOffset = 0L; - CommitLog.this.topicQueueTable.put(key, queueOffset); - } + Long queueOffset = msgInner.getQueueOffset(); - boolean multiDispatchWrapResult = CommitLog.this.multiDispatch.wrapMultiDispatch(msgInner); - if (!multiDispatchWrapResult) { - return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); - } + // this msg maybe a 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 - // consumer queuec + // 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; @@ -1346,9 +1799,9 @@ public AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer 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); + 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 + 4 + 4 + 4 + 4; @@ -1363,28 +1816,14 @@ public AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer // refresh store time stamp in lock preEncodeBuffer.putLong(pos, msgInner.getStoreTimestamp()); - 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); - AppendMessageResult result = new AppendMessageResult(AppendMessageStatus.PUT_OK, wroteOffset, msgLen, msgIdSupplier, - msgInner.getStoreTimestamp(), queueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills); - - switch (tranType) { - case MessageSysFlag.TRANSACTION_PREPARED_TYPE: - case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: - break; - case MessageSysFlag.TRANSACTION_NOT_TYPE: - case MessageSysFlag.TRANSACTION_COMMIT_TYPE: - // The next update ConsumeQueue information - CommitLog.this.topicQueueTable.put(key, ++queueOffset); - CommitLog.this.multiDispatch.updateMultiQueueOffset(msgInner); - break; - default: - break; - } - return result; + 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, @@ -1393,12 +1832,7 @@ public AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer //physical offset long wroteOffset = fileFromOffset + byteBuffer.position(); // Record ConsumeQueue information - String key = putMessageContext.getTopicQueueTableKey(); - Long queueOffset = CommitLog.this.topicQueueTable.get(key); - if (null == queueOffset) { - queueOffset = 0L; - CommitLog.this.topicQueueTable.put(key, queueOffset); - } + Long queueOffset = messageExtBatch.getQueueOffset(); long beginQueueOffset = queueOffset; int totalMsgLen = 0; int msgNum = 0; @@ -1435,13 +1869,7 @@ public AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer // 1 TOTALSIZE final int msgPos = messagesByteBuff.position(); final int msgLen = messagesByteBuff.getInt(); - final int bodyLen = msgLen - 40; //only for log, just estimate it - // Exceeds the maximum message - if (msgLen > this.maxMessageSize) { - CommitLog.log.warn("message size exceeded, msg total size: " + msgLen + ", msg body size: " + bodyLen - + ", maxMessageSize: " + this.maxMessageSize); - return new AppendMessageResult(AppendMessageStatus.MESSAGE_SIZE_EXCEEDED); - } + totalMsgLen += msgLen; // Determines whether there is sufficient free space if ((totalMsgLen + END_FILE_MIN_BLANK_LENGTH) > maxBlank) { @@ -1482,288 +1910,349 @@ public AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer AppendMessageResult result = new AppendMessageResult(AppendMessageStatus.PUT_OK, wroteOffset, totalMsgLen, msgIdSupplier, messageExtBatch.getStoreTimestamp(), beginQueueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills); result.setMsgNum(msgNum); - CommitLog.this.topicQueueTable.put(key, queueOffset); return result; } - private void resetByteBuffer(final ByteBuffer byteBuffer, final int limit) { - byteBuffer.flip(); - byteBuffer.limit(limit); - } - } - public static class MessageExtEncoder { - // Store the message content - private final ByteBuffer encoderBuffer; - // The maximum length of the message - private final int maxMessageSize; + class DefaultFlushManager implements FlushManager { - MessageExtEncoder(final int size) { - this.encoderBuffer = ByteBuffer.allocateDirect(size); - this.maxMessageSize = size; - } + private final FlushCommitLogService flushCommitLogService; + + //If TransientStorePool enabled, we must flush message to FileChannel at fixed periods + private final FlushCommitLogService commitRealTimeService; - private void 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); + public DefaultFlushManager() { + if (FlushDiskType.SYNC_FLUSH == CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) { + this.flushCommitLogService = new CommitLog.GroupCommitService(); } else { - byteBuffer.put(inetSocketAddress.getAddress().getAddress(), 0, 16); + this.flushCommitLogService = new CommitLog.FlushRealTimeService(); } - byteBuffer.putInt(inetSocketAddress.getPort()); - } - protected PutMessageResult encode(MessageExtBrokerInner msgInner) { - /** - * 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 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.getSysFlag(), bodyLength, topicLength, propertiesLength); - - // 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); - } - - // Initialization of storage space - this.resetByteBuffer(encoderBuffer, msgLen); - // 1 TOTALSIZE - this.encoderBuffer.putInt(msgLen); - // 2 MAGICCODE - this.encoderBuffer.putInt(CommitLog.MESSAGE_MAGIC_CODE); - // 3 BODYCRC - this.encoderBuffer.putInt(msgInner.getBodyCRC()); - // 4 QUEUEID - this.encoderBuffer.putInt(msgInner.getQueueId()); - // 5 FLAG - this.encoderBuffer.putInt(msgInner.getFlag()); - // 6 QUEUEOFFSET, need update later - this.encoderBuffer.putLong(0); - // 7 PHYSICALOFFSET, need update later - this.encoderBuffer.putLong(0); - // 8 SYSFLAG - this.encoderBuffer.putInt(msgInner.getSysFlag()); - // 9 BORNTIMESTAMP - this.encoderBuffer.putLong(msgInner.getBornTimestamp()); - // 10 BORNHOST - socketAddress2ByteBuffer(msgInner.getBornHost() ,this.encoderBuffer); - // 11 STORETIMESTAMP - this.encoderBuffer.putLong(msgInner.getStoreTimestamp()); - // 12 STOREHOSTADDRESS - socketAddress2ByteBuffer(msgInner.getStoreHost() ,this.encoderBuffer); - // 13 RECONSUMETIMES - this.encoderBuffer.putInt(msgInner.getReconsumeTimes()); - // 14 Prepared Transaction Offset - this.encoderBuffer.putLong(msgInner.getPreparedTransactionOffset()); - // 15 BODY - this.encoderBuffer.putInt(bodyLength); - if (bodyLength > 0) - this.encoderBuffer.put(msgInner.getBody()); - // 16 TOPIC - this.encoderBuffer.put((byte) topicLength); - this.encoderBuffer.put(topicData); - // 17 PROPERTIES - this.encoderBuffer.putShort((short) propertiesLength); - if (propertiesLength > 0) - this.encoderBuffer.put(propertiesData); - - encoderBuffer.flip(); - return null; - } - - protected ByteBuffer encode(final MessageExtBatch messageExtBatch, PutMessageContext putMessageContext) { - encoderBuffer.clear(); //not thread-safe - int totalMsgLen = 0; - ByteBuffer messagesByteBuff = messageExtBatch.wrap(); + this.commitRealTimeService = new CommitLog.CommitRealTimeService(); + } - 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); + @Override public void start() { + this.flushCommitLogService.start(); - // properties from MessageExtBatch - String batchPropStr = MessageDecoder.messageProperties2String(messageExtBatch.getProperties()); - final byte[] batchPropData = batchPropStr.getBytes(MessageDecoder.CHARSET_UTF8); - int batchPropDataLen = batchPropData.length; - if (batchPropDataLen > Short.MAX_VALUE) { - CommitLog.log.warn("Properties size of messageExtBatch exceeded, properties size: {}, maxSize: {}.", batchPropDataLen, Short.MAX_VALUE); - throw new RuntimeException("Properties size of messageExtBatch exceeded!"); + if (defaultMessageStore.isTransientStorePoolEnable()) { + this.commitRealTimeService.start(); } - final short batchPropLen = (short) batchPropDataLen; + } - 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); - boolean needAppendLastPropertySeparator = propertiesLen > 0 && batchPropLen > 0 - && messagesByteBuff.get(messagesByteBuff.position() - 1) != MessageDecoder.PROPERTY_SEPARATOR; - - final byte[] topicData = messageExtBatch.getTopic().getBytes(MessageDecoder.CHARSET_UTF8); - - final int topicLength = topicData.length; - - int totalPropLen = needAppendLastPropertySeparator ? propertiesLen + batchPropLen + 1 - : propertiesLen + batchPropLen; - final int msgLen = calMsgLength(messageExtBatch.getSysFlag(), bodyLen, topicLength, totalPropLen); - - // Exceeds the maximum message - if (msgLen > this.maxMessageSize) { - CommitLog.log.warn("message size exceeded, msg total size: " + msgLen + ", msg body size: " + bodyLen - + ", maxMessageSize: " + this.maxMessageSize); - throw new RuntimeException("message size exceeded"); + 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: " + messageExt.getTopic() + " tags: " + messageExt.getTags() + " client address: " + messageExt.getBornHostString()); + putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_DISK_TIMEOUT); + } + } else { + service.wakeup(); } - - totalMsgLen += msgLen; - // Determines whether there is sufficient free space - if (totalMsgLen > maxMessageSize) { - throw new RuntimeException("message size exceeded"); + } + // Asynchronous flush + else { + if (!CommitLog.this.defaultMessageStore.isTransientStorePoolEnable()) { + flushCommitLogService.wakeup(); + } else { + commitRealTimeService.wakeup(); } + } + } - // 1 TOTALSIZE - this.encoderBuffer.putInt(msgLen); - // 2 MAGICCODE - this.encoderBuffer.putInt(CommitLog.MESSAGE_MAGIC_CODE); - // 3 BODYCRC - this.encoderBuffer.putInt(bodyCrc); - // 4 QUEUEID - this.encoderBuffer.putInt(messageExtBatch.getQueueId()); - // 5 FLAG - this.encoderBuffer.putInt(flag); - // 6 QUEUEOFFSET - this.encoderBuffer.putLong(0); - // 7 PHYSICALOFFSET - this.encoderBuffer.putLong(0); - // 8 SYSFLAG - this.encoderBuffer.putInt(messageExtBatch.getSysFlag()); - // 9 BORNTIMESTAMP - this.encoderBuffer.putLong(messageExtBatch.getBornTimestamp()); - // 10 BORNHOST - this.resetByteBuffer(bornHostHolder, bornHostLength); - this.encoderBuffer.put(messageExtBatch.getBornHostBytes(bornHostHolder)); - // 11 STORETIMESTAMP - this.encoderBuffer.putLong(messageExtBatch.getStoreTimestamp()); - // 12 STOREHOSTADDRESS - this.resetByteBuffer(storeHostHolder, storeHostLength); - this.encoderBuffer.put(messageExtBatch.getStoreHostBytes(storeHostHolder)); - // 13 RECONSUMETIMES - this.encoderBuffer.putInt(messageExtBatch.getReconsumeTimes()); - // 14 Prepared Transaction Offset, batch does not support transaction - this.encoderBuffer.putLong(0); - // 15 BODY - this.encoderBuffer.putInt(bodyLen); - if (bodyLen > 0) - this.encoderBuffer.put(messagesByteBuff.array(), bodyPos, bodyLen); - // 16 TOPIC - this.encoderBuffer.put((byte) topicLength); - this.encoderBuffer.put(topicData); - // 17 PROPERTIES - this.encoderBuffer.putShort((short) totalPropLen); - if (propertiesLen > 0) { - this.encoderBuffer.put(messagesByteBuff.array(), propertiesPos, propertiesLen); + @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); } - if (batchPropLen > 0) { - if (needAppendLastPropertySeparator) { - this.encoderBuffer.put((byte) MessageDecoder.PROPERTY_SEPARATOR); - } - this.encoderBuffer.put(batchPropData, 0, batchPropLen); + } + // Asynchronous flush + else { + if (!CommitLog.this.defaultMessageStore.isTransientStorePoolEnable()) { + flushCommitLogService.wakeup(); + } else { + commitRealTimeService.wakeup(); } + return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK); } - putMessageContext.setBatchSize(batchSize); - putMessageContext.setPhyPos(new long[batchSize]); - encoderBuffer.flip(); - return encoderBuffer; } - private void resetByteBuffer(final ByteBuffer byteBuffer, final int limit) { - byteBuffer.flip(); - byteBuffer.limit(limit); + @Override + public void wakeUpFlush() { + // now wake up flush thread. + flushCommitLogService.wakeup(); + } + + @Override + public void wakeUpCommit() { + // now wake up commit log thread. + commitRealTimeService.wakeup(); } - public ByteBuffer getEncoderBuffer() { - return encoderBuffer; + @Override + public void shutdown() { + if (defaultMessageStore.isTransientStorePoolEnable()) { + this.commitRealTimeService.shutdown(); + } + + this.flushCommitLogService.shutdown(); } + } - static class PutMessageThreadLocal { - private MessageExtEncoder encoder; - private StringBuilder keyBuilder; - PutMessageThreadLocal(int size) { - encoder = new MessageExtEncoder(size); - keyBuilder = new StringBuilder(); + public int getCommitLogSize() { + return commitLogSize; + } + + public MappedFileQueue getMappedFileQueue() { + return mappedFileQueue; + } + + public MessageStore getMessageStore() { + return defaultMessageStore; + } + + @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(); } - public MessageExtEncoder getEncoder() { - return encoder; + @Override + public String getServiceName() { + return ColdDataCheckService.class.getSimpleName(); } - public StringBuilder getKeyBuilder() { - return keyBuilder; + @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()); + } + 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(this.getServiceName() + " service has e: {}", 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; } - } - static class PutMessageContext { - private String topicQueueTableKey; - private long[] phyPos; - private int batchSize; + 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); + } + } - public PutMessageContext(String topicQueueTableKey) { - this.topicQueueTableKey = topicQueueTableKey; + 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); + } + }); } - public String getTopicQueueTableKey() { - return topicQueueTableKey; + 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 = ((DirectBuffer)mappedFile.getMappedByteBuffer()).address(); + 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; } - public long[] getPhyPos() { - return phyPos; + private void initPageSize() { + if (pageSize < 0) { + 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); + } + } } - public void setPhyPos(long[] phyPos) { - this.phyPos = phyPos; + /** + * this result is not high accurate. + */ + public boolean isMsgInColdArea(String group, String topic, int queueId, long offset) { + if (!defaultMessageStore.getMessageStoreConfig().isColdDataFlowControlEnable()) { + return false; + } + try { + ConsumeQueue consumeQueue = (ConsumeQueue)defaultMessageStore.findConsumeQueue(topic, queueId); + if (null == consumeQueue) { + return false; + } + SelectMappedBufferResult bufferConsumeQueue = consumeQueue.getIndexBuffer(offset); + if (null == bufferConsumeQueue || null == bufferConsumeQueue.getByteBuffer()) { + return false; + } + long offsetPy = bufferConsumeQueue.getByteBuffer().getLong(); + return defaultMessageStore.checkInColdAreaByCommitOffset(offsetPy, getMaxOffset()); + } catch (Exception e) { + log.error("isMsgInColdArea group: {}, topic: {}, queueId: {}, offset: {}", + group, topic, queueId, offset, e); + } + return false; } + } - public int getBatchSize() { - return batchSize; + 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); + } + } - public void setBatchSize(int batchSize) { - this.batchSize = batchSize; + private int setFileReadMode(MappedFile mappedFile, int mode) { + if (null == mappedFile) { + log.error("setFileReadMode mappedFile is null"); + return -1; } + final long address = ((DirectBuffer)mappedFile.getMappedByteBuffer()).address(); + 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; } } diff --git a/store/src/main/java/org/apache/rocketmq/store/CompactionAppendMsgCallback.java b/store/src/main/java/org/apache/rocketmq/store/CompactionAppendMsgCallback.java new file mode 100644 index 00000000000..1667144401e --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/CompactionAppendMsgCallback.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java b/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java index fdc725db7f5..0c44ad043fc 100644 --- a/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java +++ b/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java @@ -18,24 +18,51 @@ 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.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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.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.CqUnit; +import org.apache.rocketmq.store.queue.FileQueueLifeCycle; +import org.apache.rocketmq.store.queue.QueueOffsetOperator; +import org.apache.rocketmq.store.queue.ReferredIterator; +import org.apache.rocketmq.store.timer.TimerMessageStore; -public class ConsumeQueue { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); +public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle { + 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; - private static final InternalLogger LOG_ERROR = InternalLoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); + public static final int MSG_TAG_OFFSET_INDEX = 12; + private static final Logger LOG_ERROR = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); - private final DefaultMessageStore defaultMessageStore; + private final MessageStore messageStore; private final MappedFileQueue mappedFileQueue; private final String topic; @@ -45,6 +72,10 @@ public class ConsumeQueue { 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; @@ -53,10 +84,10 @@ public ConsumeQueue( final int queueId, final String storePath, final int mappedFileSize, - final DefaultMessageStore defaultMessageStore) { + final MessageStore messageStore) { this.storePath = storePath; this.mappedFileSize = mappedFileSize; - this.defaultMessageStore = defaultMessageStore; + this.messageStore = messageStore; this.topic = topic; this.queueId = queueId; @@ -69,17 +100,18 @@ public ConsumeQueue( this.byteBufferIndex = ByteBuffer.allocate(CQ_STORE_UNIT_SIZE); - if (defaultMessageStore.getMessageStoreConfig().isEnableConsumeQueueExt()) { + if (messageStore.getMessageStoreConfig().isEnableConsumeQueueExt()) { this.consumeQueueExt = new ConsumeQueueExt( topic, queueId, - StorePathConfigHelper.getStorePathConsumeQueueExt(defaultMessageStore.getMessageStoreConfig().getStorePathRootDir()), - defaultMessageStore.getMessageStoreConfig().getMappedFileSizeConsumeQueueExt(), - defaultMessageStore.getMessageStoreConfig().getBitMapLengthConsumeQueueExt() + StorePathConfigHelper.getStorePathConsumeQueueExt(messageStore.getMessageStoreConfig().getStorePathRootDir()), + messageStore.getMessageStoreConfig().getMappedFileSizeConsumeQueueExt(), + messageStore.getMessageStoreConfig().getBitMapLengthConsumeQueueExt() ); } } + @Override public boolean load() { boolean result = this.mappedFileQueue.load(); log.info("load consume queue " + this.topic + "-" + this.queueId + " " + (result ? "OK" : "Failed")); @@ -89,13 +121,15 @@ public boolean load() { return result; } + @Override public void recover() { final List mappedFiles = this.mappedFileQueue.getMappedFiles(); if (!mappedFiles.isEmpty()) { int index = mappedFiles.size() - 3; - if (index < 0) + if (index < 0) { index = 0; + } int mappedFileSizeLogics = this.mappedFileSize; MappedFile mappedFile = mappedFiles.get(index); @@ -137,7 +171,7 @@ public void recover() { log.info("recover next consume queue file, " + mappedFile.getFileName()); } } else { - log.info("recover current consume queue queue over " + mappedFile.getFileName() + " " + log.info("recover current consume queue over " + mappedFile.getFileName() + " " + (processOffset + mappedFileOffset)); break; } @@ -156,34 +190,108 @@ public void recover() { } } + @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; + } + + @Override public long getOffsetInQueueByTime(final long timestamp) { - MappedFile mappedFile = this.mappedFileQueue.getMappedFileByTime(timestamp); + MappedFile mappedFile = this.mappedFileQueue.getConsumeQueueMappedFileByTime(timestamp, + messageStore.getCommitLog(), BoundaryType.LOWER); + return binarySearchInQueueByTime(mappedFile, timestamp, BoundaryType.LOWER); + } + + 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 leftIndexValue = -1L, rightIndexValue = -1L; - long minPhysicOffset = this.defaultMessageStore.getMinPhyOffset(); - SelectMappedBufferResult sbr = mappedFile.selectMappedBuffer(0); + 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(); - high = byteBuffer.limit() - CQ_STORE_UNIT_SIZE; + 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); - long phyOffset = byteBuffer.getLong(); - int size = byteBuffer.getInt(); + phyOffset = byteBuffer.getLong(); + size = byteBuffer.getInt(); if (phyOffset < minPhysicOffset) { low = midOffset + CQ_STORE_UNIT_SIZE; leftOffset = midOffset; continue; } - long storeTime = - this.defaultMessageStore.getCommitLog().pickupStoreTimestamp(phyOffset, size); + 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; @@ -191,31 +299,96 @@ public long getOffsetInQueueByTime(final long timestamp) { } else if (storeTime > timestamp) { high = midOffset - CQ_STORE_UNIT_SIZE; rightOffset = midOffset; - rightIndexValue = storeTime; } else { low = midOffset + CQ_STORE_UNIT_SIZE; leftOffset = midOffset; - leftIndexValue = storeTime; } } 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 { - if (leftIndexValue == -1) { - - offset = rightOffset; - } else if (rightIndexValue == -1) { + // 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; + } - offset = leftOffset; - } else { - offset = - Math.abs(timestamp - leftIndexValue) > Math.abs(timestamp - - rightIndexValue) ? rightOffset : leftOffset; + case UPPER: { + offset = leftOffset; + break; + } + default: { + log.warn("Unknown boundary type"); + break; + } } } - return (mappedFile.getFileFromOffset() + offset) / CQ_STORE_UNIT_SIZE; } finally { sbr.release(); @@ -225,12 +398,18 @@ public long getOffsetInQueueByTime(final long timestamp) { return 0; } - public void truncateDirtyLogicFiles(long phyOffet) { + @Override + public void truncateDirtyLogicFiles(long phyOffset) { + truncateDirtyLogicFiles(phyOffset, true); + } + + public void truncateDirtyLogicFiles(long phyOffset, boolean deleteFile) { int logicFileSize = this.mappedFileSize; - this.maxPhysicOffset = phyOffet; + this.maxPhysicOffset = phyOffset; long maxExtAddr = 1; + boolean shouldDeleteFile = false; while (true) { MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); if (mappedFile != null) { @@ -246,8 +425,8 @@ public void truncateDirtyLogicFiles(long phyOffet) { long tagsCode = byteBuffer.getLong(); if (0 == i) { - if (offset >= phyOffet) { - this.mappedFileQueue.deleteLastMappedFile(); + if (offset >= phyOffset) { + shouldDeleteFile = true; break; } else { int pos = i + CQ_STORE_UNIT_SIZE; @@ -264,7 +443,7 @@ public void truncateDirtyLogicFiles(long phyOffet) { if (offset >= 0 && size > 0) { - if (offset >= phyOffet) { + if (offset >= phyOffset) { return; } @@ -285,6 +464,15 @@ public void truncateDirtyLogicFiles(long phyOffet) { } } } + + if (shouldDeleteFile) { + if (deleteFile) { + this.mappedFileQueue.deleteLastMappedFile(); + } else { + this.mappedFileQueue.deleteExpiredFile(Collections.singletonList(this.mappedFileQueue.getLastMappedFile())); + } + } + } else { break; } @@ -295,6 +483,7 @@ public void truncateDirtyLogicFiles(long phyOffet) { } } + @Override public long getLastOffset() { long lastOffset = -1; @@ -325,6 +514,7 @@ public long getLastOffset() { return lastOffset; } + @Override public boolean flush(final int flushLeastPages) { boolean result = this.mappedFileQueue.flush(flushLeastPages); if (isExtReadEnable()) { @@ -334,40 +524,142 @@ public boolean flush(final int flushLeastPages) { return result; } + @Override public int deleteExpiredFile(long offset) { int cnt = this.mappedFileQueue.deleteExpiredFileByOffset(offset, CQ_STORE_UNIT_SIZE); this.correctMinOffset(offset); return cnt; } - public void correctMinOffset(long phyMinOffset) { + /** + * 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(); + 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) { - SelectMappedBufferResult result = mappedFile.selectMappedBuffer(0); - if (result != null) { - try { - for (int i = 0; i < result.getSize(); i += ConsumeQueue.CQ_STORE_UNIT_SIZE) { - long offsetPy = result.getByteBuffer().getLong(); - result.getByteBuffer().getInt(); - long tagsCode = result.getByteBuffer().getLong(); - - if (offsetPy >= phyMinOffset) { - this.minLogicOffset = mappedFile.getFileFromOffset() + 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 extend file. - if (isExtAddr(tagsCode)) { - minExtAddr = tagsCode; - } - break; + // 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(); } + } catch (Exception e) { + log.error("Exception thrown when correctMinOffset", e); + } finally { + result.release(); } } @@ -376,13 +668,15 @@ public void correctMinOffset(long phyMinOffset) { } } + @Override public long getMinOffsetInQueue() { return this.minLogicOffset / CQ_STORE_UNIT_SIZE; } - public void putMessagePositionInfoWrapper(DispatchRequest request, boolean multiQueue) { + @Override + public void putMessagePositionInfoWrapper(DispatchRequest request) { final int maxRetries = 30; - boolean canWrite = this.defaultMessageStore.getRunningFlags().isCQWriteable(); + boolean canWrite = this.messageStore.getRunningFlags().isCQWriteable(); for (int i = 0; i < maxRetries && canWrite; i++) { long tagsCode = request.getTagsCode(); if (isExtWriteEnable()) { @@ -402,12 +696,12 @@ public void putMessagePositionInfoWrapper(DispatchRequest request, boolean multi boolean result = this.putMessagePositionInfo(request.getCommitLogOffset(), request.getMsgSize(), tagsCode, request.getConsumeQueueOffset()); if (result) { - if (this.defaultMessageStore.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE || - this.defaultMessageStore.getMessageStoreConfig().isEnableDLegerCommitLog()) { - this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(request.getStoreTimestamp()); + if (this.messageStore.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE || + this.messageStore.getMessageStoreConfig().isEnableDLegerCommitLog()) { + this.messageStore.getStoreCheckpoint().setPhysicMsgTimestamp(request.getStoreTimestamp()); } - this.defaultMessageStore.getStoreCheckpoint().setLogicsMsgTimestamp(request.getStoreTimestamp()); - if (multiQueue) { + this.messageStore.getStoreCheckpoint().setLogicsMsgTimestamp(request.getStoreTimestamp()); + if (checkMultiDispatchQueue(request)) { multiDispatchLmqQueue(request, maxRetries); } return; @@ -426,7 +720,26 @@ public void putMessagePositionInfoWrapper(DispatchRequest request, boolean multi // XXX: warn and notify me log.error("[BUG]consume queue can not write, {} {}", this.topic, this.queueId); - this.defaultMessageStore.getRunningFlags().makeLogicsQueueError(); + this.messageStore.getRunningFlags().makeLogicsQueueError(); + } + + private boolean checkMultiDispatchQueue(DispatchRequest dispatchRequest) { + if (!this.messageStore.getMessageStoreConfig().isEnableMultiDispatch() + || dispatchRequest.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) + || dispatchRequest.getTopic().equals(TimerMessageStore.TIMER_TOPIC) + || dispatchRequest.getTopic().equals(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC)) { + 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; } private void multiDispatchLmqQueue(DispatchRequest request, int maxRetries) { @@ -441,9 +754,12 @@ private void multiDispatchLmqQueue(DispatchRequest request, int maxRetries) { } 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.defaultMessageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queueName)) { + if (this.messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queueName)) { queueId = 0; } doDispatchLmqQueue(request, maxRetries, queueName, queueOffset, queueId); @@ -454,10 +770,10 @@ private void multiDispatchLmqQueue(DispatchRequest request, int maxRetries) { private void doDispatchLmqQueue(DispatchRequest request, int maxRetries, String queueName, long queueOffset, int queueId) { - ConsumeQueue cq = this.defaultMessageStore.findConsumeQueue(queueName, queueId); - boolean canWrite = this.defaultMessageStore.getRunningFlags().isCQWriteable(); + ConsumeQueueInterface cq = this.messageStore.findConsumeQueue(queueName, queueId); + boolean canWrite = this.messageStore.getRunningFlags().isCQWriteable(); for (int i = 0; i < maxRetries && canWrite; i++) { - boolean result = cq.putMessagePositionInfo(request.getCommitLogOffset(), request.getMsgSize(), + boolean result = ((ConsumeQueue) cq).putMessagePositionInfo(request.getCommitLogOffset(), request.getMsgSize(), request.getTagsCode(), queueOffset); if (result) { @@ -475,6 +791,91 @@ private void doDispatchLmqQueue(DispatchRequest request, int maxRetries, String } } + @Override + public void assignQueueOffset(QueueOffsetOperator queueOffsetOperator, MessageExtBrokerInner msg) { + String topicQueueKey = getTopic() + "-" + getQueueId(); + long queueOffset = queueOffsetOperator.getQueueOffset(topicQueueKey); + msg.setQueueOffset(queueOffset); + + + // Handling the multi dispatch message. In the context of a light message queue (as defined in RIP-28), + // light message queues are constructed based on message properties, which requires special handling of queue offset of the light message queue. + if (!isNeedHandleMultiDispatch(msg)) { + return; + } + String multiDispatchQueue = msg.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); + if (StringUtils.isBlank(multiDispatchQueue)) { + return; + } + String[] queues = multiDispatchQueue.split(MixAll.MULTI_DISPATCH_QUEUE_SPLITTER); + Long[] queueOffsets = new Long[queues.length]; + for (int i = 0; i < queues.length; i++) { + String key = queueKey(queues[i], msg); + if (messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(key)) { + queueOffsets[i] = queueOffsetOperator.getLmqOffset(key); + } + } + MessageAccessor.putProperty(msg, MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET, + StringUtils.join(queueOffsets, MixAll.MULTI_DISPATCH_QUEUE_SPLITTER)); + removeWaitStorePropertyString(msg); + } + + @Override + public void increaseQueueOffset(QueueOffsetOperator queueOffsetOperator, MessageExtBrokerInner msg, + short messageNum) { + String topicQueueKey = getTopic() + "-" + getQueueId(); + queueOffsetOperator.increaseQueueOffset(topicQueueKey, messageNum); + + // Handling the multi dispatch message. In the context of a light message queue (as defined in RIP-28), + // light message queues are constructed based on message properties, which requires special handling of queue offset of the light message queue. + if (!isNeedHandleMultiDispatch(msg)) { + return; + } + String multiDispatchQueue = msg.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); + if (StringUtils.isBlank(multiDispatchQueue)) { + return; + } + String[] queues = multiDispatchQueue.split(MixAll.MULTI_DISPATCH_QUEUE_SPLITTER); + for (int i = 0; i < queues.length; i++) { + String key = queueKey(queues[i], msg); + if (messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(key)) { + queueOffsetOperator.increaseLmqOffset(key, (short) 1); + } + } + } + + public boolean isNeedHandleMultiDispatch(MessageExtBrokerInner msg) { + return messageStore.getMessageStoreConfig().isEnableMultiDispatch() + && !msg.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) + && !msg.getTopic().equals(TimerMessageStore.TIMER_TOPIC) + && !msg.getTopic().equals(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC); + } + + public String queueKey(String queueName, MessageExtBrokerInner msgInner) { + StringBuilder keyBuilder = new StringBuilder(); + keyBuilder.append(queueName); + keyBuilder.append('-'); + int queueId = msgInner.getQueueId(); + if (messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queueName)) { + queueId = 0; + } + keyBuilder.append(queueId); + return keyBuilder.toString(); + } + + private void removeWaitStorePropertyString(MessageExtBrokerInner msgInner) { + if (msgInner.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 = msgInner.getProperties().remove(MessageConst.PROPERTY_WAIT_STORE_MSG_OK); + msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); + // Reput to properties, since msgInner.isWaitStoreMsgOK() will be invoked later + msgInner.getProperties().put(MessageConst.PROPERTY_WAIT_STORE_MSG_OK, waitStoreMsgOKValue); + } else { + msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); + } + } + private boolean putMessagePositionInfo(final long offset, final int size, final long tagsCode, final long cqOffset) { @@ -547,13 +948,130 @@ public SelectMappedBufferResult getIndexBuffer(final long startIndex) { if (offset >= this.getMinLogicOffset()) { MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(offset); if (mappedFile != null) { - SelectMappedBufferResult result = mappedFile.selectMappedBuffer((int) (offset % mappedFileSize)); - return result; + 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 CqUnit get(long offset) { + ReferredIterator it = iterateFrom(offset); + if (it == null) { + return null; + } + return it.nextAndRelease(); + } + + @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); @@ -568,6 +1086,7 @@ public boolean getExt(final long offset, ConsumeQueueExt.CqExtUnit cqExtUnit) { return false; } + @Override public long getMinLogicOffset() { return minLogicOffset; } @@ -576,20 +1095,29 @@ public void setMinLogicOffset(long minLogicOffset) { this.minLogicOffset = minLogicOffset; } - public long rollNextFile(final long index) { + @Override + public long rollNextFile(final long nextBeginOffset) { int mappedFileSize = this.mappedFileSize; int totalUnitsInFile = mappedFileSize / CQ_STORE_UNIT_SIZE; - return index + totalUnitsInFile - index % totalUnitsInFile; + 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; } @@ -598,6 +1126,7 @@ public void setMaxPhysicOffset(long maxPhysicOffset) { this.maxPhysicOffset = maxPhysicOffset; } + @Override public void destroy() { this.maxPhysicOffset = -1; this.minLogicOffset = 0; @@ -607,14 +1136,17 @@ public void 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()) { @@ -628,7 +1160,7 @@ protected boolean isExtReadEnable() { protected boolean isExtWriteEnable() { return this.consumeQueueExt != null - && this.defaultMessageStore.getMessageStoreConfig().isEnableConsumeQueueExt(); + && this.messageStore.getMessageStoreConfig().isEnableConsumeQueueExt(); } /** @@ -638,4 +1170,100 @@ 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; + } } diff --git a/store/src/main/java/org/apache/rocketmq/store/ConsumeQueueExt.java b/store/src/main/java/org/apache/rocketmq/store/ConsumeQueueExt.java index 117a70b7138..780505c53d1 100644 --- a/store/src/main/java/org/apache/rocketmq/store/ConsumeQueueExt.java +++ b/store/src/main/java/org/apache/rocketmq/store/ConsumeQueueExt.java @@ -18,14 +18,15 @@ package org.apache.rocketmq.store; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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, @@ -37,7 +38,7 @@ *
  • 4. Pls keep this file small.
  • */ public class ConsumeQueueExt { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final MappedFileQueue mappedFileQueue; private final String topic; @@ -89,6 +90,10 @@ public ConsumeQueueExt(final String topic, } } + public long getTotalSize() { + return this.mappedFileQueue.getTotalFileSize(); + } + /** * Check whether {@code address} point to extend file. *

    @@ -321,7 +326,7 @@ public void truncateByMinAddress(final long minAddress) { log.info("Truncate consume queue ext by min {}.", minAddress); - List willRemoveFiles = new ArrayList(); + List willRemoveFiles = new ArrayList<>(); List mappedFiles = this.mappedFileQueue.getMappedFiles(); final long realOffset = unDecorate(minAddress); diff --git a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageFilter.java b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageFilter.java index 9db87f31960..fff6966c22d 100644 --- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageFilter.java +++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageFilter.java @@ -16,10 +16,9 @@ */ package org.apache.rocketmq.store; -import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; - import java.nio.ByteBuffer; import java.util.Map; +import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData; public class DefaultMessageFilter implements MessageFilter { diff --git a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java index f11d5f314bd..acc1610e089 100644 --- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java +++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java @@ -16,6 +16,13 @@ */ 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.View; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; @@ -24,60 +31,95 @@ 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.Iterator; import java.util.LinkedList; +import java.util.List; import java.util.Map; -import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; - +import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; +import org.apache.rocketmq.common.AbstractBrokerRunnable; 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.CQType; +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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.common.utils.CleanupPolicyUtils; +import org.apache.rocketmq.common.utils.QueueTypeUtils; +import org.apache.rocketmq.common.utils.ServiceProvider; +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.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.schedule.ScheduleMessageService; +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.metrics.DefaultStoreMetricsManager; +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.ReferredIterator; import org.apache.rocketmq.store.stats.BrokerStatsManager; +import org.apache.rocketmq.store.timer.TimerMessageStore; +import org.apache.rocketmq.store.util.PerfCounter; public class DefaultMessageStore implements MessageStore { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + + public final PerfCounter.Ticks perfs = new PerfCounter.Ticks(LOGGER); private final MessageStoreConfig messageStoreConfig; // CommitLog private final CommitLog commitLog; - private final ConcurrentMap> consumeQueueTable; + private final ConsumeQueueStore consumeQueueStore; private final FlushConsumeQueueService flushConsumeQueueService; @@ -85,15 +127,20 @@ public class DefaultMessageStore implements MessageStore { private final CleanConsumeQueueService cleanConsumeQueueService; + private final CorrectLogicOffsetService correctLogicOffsetService; + private final IndexService indexService; private final AllocateMappedFileService allocateMappedFileService; - private final ReputMessageService reputMessageService; + private ReputMessageService reputMessageService; + + private HAService haService; - private final HAService haService; + // CompactionLog + private CompactionStore compactionStore; - private final ScheduleMessageService scheduleMessageService; + private CompactionService compactionService; private final StoreStatsService storeStatsService; @@ -102,8 +149,7 @@ public class DefaultMessageStore implements MessageStore { private final RunningFlags runningFlags = new RunningFlags(); private final SystemClock systemClock = new SystemClock(); - private final ScheduledExecutorService scheduledExecutorService = - Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("StoreScheduledThread")); + private final ScheduledExecutorService scheduledExecutorService; private final BrokerStatsManager brokerStatsManager; private final MessageArrivingListener messageArrivingListener; private final BrokerConfig brokerConfig; @@ -111,10 +157,7 @@ public class DefaultMessageStore implements MessageStore { private volatile boolean shutdown = true; private StoreCheckpoint storeCheckpoint; - - private AtomicLong printTimes = new AtomicLong(0); - - private final AtomicInteger lmqConsumeQueueNum = new AtomicInteger(0); + private TimerMessageStore timerMessageStore; private final LinkedList dispatcherList; @@ -123,102 +166,188 @@ public class DefaultMessageStore implements MessageStore { private FileLock lock; boolean shutDownNormal = false; + // Max pull msg size + private final static int MAX_PULL_MSG_SIZE = 128 * 1024 * 1024; - private final ScheduledExecutorService diskCheckScheduledExecutorService = - Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("DiskCheckScheduledThread")); + 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; + + protected List putMessageHookList = new ArrayList<>(); + + private SendMessageBackHook sendMessageBackHook; + + private final ConcurrentMap delayLevelTable = + new ConcurrentHashMap<>(32); + + private int maxDelayLevel; + + private final AtomicInteger mappedPageHoldCount = new AtomicInteger(0); + + private final ConcurrentLinkedQueue batchDispatchRequestQueue = new ConcurrentLinkedQueue<>(); + + private int dispatchRequestOrderlyQueueSize = 16; + + private final DispatchRequestOrderlyQueue dispatchRequestOrderlyQueue = new DispatchRequestOrderlyQueue(dispatchRequestOrderlyQueueSize); + + private long stateMachineVersion = 0L; + + // this is a unmodifiableMap + private ConcurrentMap topicConfigTable; + + private final ScheduledExecutorService scheduledCleanQueueExecutorService = + Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("StoreCleanQueueScheduledThread")); public DefaultMessageStore(final MessageStoreConfig messageStoreConfig, final BrokerStatsManager brokerStatsManager, - final MessageArrivingListener messageArrivingListener, final BrokerConfig brokerConfig) throws IOException { + final MessageArrivingListener messageArrivingListener, final BrokerConfig brokerConfig, final ConcurrentMap topicConfigTable) throws IOException { this.messageArrivingListener = messageArrivingListener; this.brokerConfig = brokerConfig; this.messageStoreConfig = messageStoreConfig; + this.aliveReplicasNum = messageStoreConfig.getTotalReplicas(); this.brokerStatsManager = brokerStatsManager; + this.topicConfigTable = topicConfigTable; this.allocateMappedFileService = new AllocateMappedFileService(this); if (messageStoreConfig.isEnableDLegerCommitLog()) { this.commitLog = new DLedgerCommitLog(this); } else { this.commitLog = new CommitLog(this); } - this.consumeQueueTable = new ConcurrentHashMap<>(32); + + this.consumeQueueStore = new ConsumeQueueStore(this, this.messageStoreConfig); this.flushConsumeQueueService = new FlushConsumeQueueService(); this.cleanCommitLogService = new CleanCommitLogService(); this.cleanConsumeQueueService = new CleanConsumeQueueService(); - this.storeStatsService = new StoreStatsService(); + this.correctLogicOffsetService = new CorrectLogicOffsetService(); + this.storeStatsService = new StoreStatsService(getBrokerIdentity()); this.indexService = new IndexService(this); - if (!messageStoreConfig.isEnableDLegerCommitLog()) { - this.haService = new HAService(this); - } else { - this.haService = null; - } - this.reputMessageService = new ReputMessageService(); - - this.scheduleMessageService = new ScheduleMessageService(this); - this.transientStorePool = new TransientStorePool(messageStoreConfig); + if (!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()); + } + } + } - if (messageStoreConfig.isTransientStorePoolEnable()) { - this.transientStorePool.init(); + if (!messageStoreConfig.isEnableBuildConsumeQueueConcurrently()) { + this.reputMessageService = new ReputMessageService(); + } else { + this.reputMessageService = new ConcurrentReputMessageService(); } - this.allocateMappedFileService.start(); + this.transientStorePool = new TransientStorePool(this); - this.indexService.start(); + this.scheduledExecutorService = + Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("StoreScheduledThread", getBrokerIdentity())); this.dispatcherList = new LinkedList<>(); this.dispatcherList.addLast(new CommitLogDispatcherBuildConsumeQueue()); this.dispatcherList.addLast(new CommitLogDispatcherBuildIndex()); + 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())); - MappedFile.ensureDirOK(file.getParent()); - MappedFile.ensureDirOK(getStorePathPhysic()); - MappedFile.ensureDirOK(getStorePathLogic()); + UtilAll.ensureDirOK(file.getParent()); + UtilAll.ensureDirOK(getStorePathPhysic()); + UtilAll.ensureDirOK(getStorePathLogic()); lockFile = new RandomAccessFile(file, "rw"); + + parseDelayLevel(); } - public void truncateDirtyLogicFiles(long phyOffset) { - ConcurrentMap> tables = DefaultMessageStore.this.consumeQueueTable; + 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); - for (ConcurrentMap maps : tables.values()) { - for (ConsumeQueue logic : maps.values()) { - logic.truncateDirtyLogicFiles(phyOffset); + 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; + } + + @Override + public void truncateDirtyLogicFiles(long phyOffset) { + this.consumeQueueStore.truncateDirty(phyOffset); } /** * @throws IOException */ + @Override public boolean load() { boolean result = true; try { boolean lastExitOK = !this.isTempFileExist(); - log.info("last shutdown {}", lastExitOK ? "normally" : "abnormally"); + LOGGER.info("last shutdown {}, store path root dir: {}", + lastExitOK ? "normally" : "abnormally", messageStoreConfig.getStorePathRootDir()); // load Commit Log - result = result && this.commitLog.load(); + result = this.commitLog.load(); // load Consume Queue - result = result && this.loadConsumeQueue(); + result = result && this.consumeQueueStore.load(); + + if (messageStoreConfig.isEnableCompaction()) { + result = result && this.compactionService.load(lastExitOK); + } if (result) { this.storeCheckpoint = - new StoreCheckpoint(StorePathConfigHelper.getStoreCheckpoint(this.messageStoreConfig.getStorePathRootDir())); - - this.indexService.load(lastExitOK); + new StoreCheckpoint( + StorePathConfigHelper.getStoreCheckpoint(this.messageStoreConfig.getStorePathRootDir())); + this.masterFlushedOffset = this.storeCheckpoint.getMasterFlushedOffset(); + setConfirmOffset(this.storeCheckpoint.getConfirmPhyOffset()); + result = this.indexService.load(lastExitOK); this.recover(lastExitOK); - - log.info("load over, and the max phy offset = {}", this.getMaxPhyOffset()); - - if (null != scheduleMessageService) { - result = this.scheduleMessageService.load(); - } + 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) { - log.error("load exception", e); + LOGGER.error("load exception", e); result = false; } @@ -232,112 +361,143 @@ public boolean load() { /** * @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"); } - lockFile.getChannel().write(ByteBuffer.wrap("lock".getBytes())); + lockFile.getChannel().write(ByteBuffer.wrap("lock".getBytes(StandardCharsets.UTF_8))); lockFile.getChannel().force(true); - { - /** - * 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.consumeQueueTable.values()) { - for (ConsumeQueue logic : maps.values()) { - if (logic.getMaxPhysicOffset() > maxPhysicalPosInLogicQueue) { - maxPhysicalPosInLogicQueue = logic.getMaxPhysicOffset(); - } - } - } - 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. - */ - log.warn("[TooSmallCqOffset] maxPhysicalPosInLogicQueue={} clMinOffset={}", maxPhysicalPosInLogicQueue, this.commitLog.getMinOffset()); - } - log.info("[SetReputOffset] maxPhysicalPosInLogicQueue={} clMinOffset={} clMaxOffset={} clConfirmedOffset={}", - maxPhysicalPosInLogicQueue, this.commitLog.getMinOffset(), this.commitLog.getMaxOffset(), this.commitLog.getConfirmOffset()); - this.reputMessageService.setReputFromOffset(maxPhysicalPosInLogicQueue); - this.reputMessageService.start(); - /** - * 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); - log.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(); - } + this.reputMessageService.setReputFromOffset(this.commitLog.getConfirmOffset()); + this.reputMessageService.start(); - if (!messageStoreConfig.isEnableDLegerCommitLog()) { - this.haService.start(); - this.handleScheduleMessageService(messageStoreConfig.getBrokerRole()); - } + // 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.flushConsumeQueueService.start(); this.commitLog.start(); this.storeStatsService.start(); + if (this.haService != null) { + this.haService.start(); + } + this.createTempFile(); this.addScheduleTask(); + this.perfs.start(); this.shutdown = false; } + 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.shutdown) { this.shutdown = true; this.scheduledExecutorService.shutdown(); - this.diskCheckScheduledExecutorService.shutdown(); - try { + this.scheduledCleanQueueExecutorService.shutdown(); - Thread.sleep(1000); + try { + this.scheduledExecutorService.awaitTermination(3, TimeUnit.SECONDS); + this.scheduledCleanQueueExecutorService.awaitTermination(3, TimeUnit.SECONDS); + Thread.sleep(1000 * 3); } catch (InterruptedException e) { - log.error("shutdown Exception, ", e); + LOGGER.error("shutdown Exception, ", e); } - if (this.scheduleMessageService != null) { - this.scheduleMessageService.shutdown(); - } if (this.haService != null) { this.haService.shutdown(); } this.storeStatsService.shutdown(); - this.indexService.shutdown(); this.commitLog.shutdown(); this.reputMessageService.shutdown(); + // dispatch-related services must be shut down after reputMessageService + this.indexService.shutdown(); + if (this.compactionService != null) { + this.compactionService.shutdown(); + } + this.flushConsumeQueueService.shutdown(); this.allocateMappedFileService.shutdown(); this.storeCheckpoint.flush(); this.storeCheckpoint.shutdown(); + this.perfs.shutdown(); + if (this.runningFlags.isWriteable() && dispatchBehindBytes() == 0) { this.deleteFile(StorePathConfigHelper.getAbortFile(this.messageStoreConfig.getStorePathRootDir())); shutDownNormal = true; } else { - log.warn("the store may be wrong, so shutdown abnormally, and keep abort file."); + LOGGER.warn("the store may be wrong, so shutdown abnormally, and keep abort file."); } } @@ -352,6 +512,7 @@ public void shutdown() { } } + @Override public void destroy() { this.destroyLogics(); this.commitLog.destroy(); @@ -360,114 +521,62 @@ public void destroy() { this.deleteFile(StorePathConfigHelper.getStoreCheckpoint(this.messageStoreConfig.getStorePathRootDir())); } - public void destroyLogics() { - for (ConcurrentMap maps : this.consumeQueueTable.values()) { - for (ConsumeQueue logic : maps.values()) { - logic.destroy(); - } - } - } - - private PutMessageStatus checkMessage(MessageExtBrokerInner msg) { - if (msg.getTopic().length() > Byte.MAX_VALUE) { - log.warn("putMessage message topic length too long " + msg.getTopic().length()); - return PutMessageStatus.MESSAGE_ILLEGAL; - } - - if (msg.getPropertiesString() != null && msg.getPropertiesString().length() > Short.MAX_VALUE) { - log.warn("putMessage message properties length too long " + msg.getPropertiesString().length()); - return PutMessageStatus.MESSAGE_ILLEGAL; - } - return PutMessageStatus.PUT_OK; - } - - private PutMessageStatus checkMessages(MessageExtBatch messageExtBatch) { - if (messageExtBatch.getTopic().length() > Byte.MAX_VALUE) { - log.warn("putMessage message topic length too long " + messageExtBatch.getTopic().length()); - return PutMessageStatus.MESSAGE_ILLEGAL; - } - - if (messageExtBatch.getBody().length > messageStoreConfig.getMaxMessageSize()) { - log.warn("PutMessages body length too long " + messageExtBatch.getBody().length); - return PutMessageStatus.MESSAGE_ILLEGAL; - } - - return PutMessageStatus.PUT_OK; - } - - private PutMessageStatus checkStoreStatus() { - if (this.shutdown) { - log.warn("message store has shutdown, so putMessage is forbidden"); - return PutMessageStatus.SERVICE_NOT_AVAILABLE; - } - - if (BrokerRole.SLAVE == this.messageStoreConfig.getBrokerRole()) { - long value = this.printTimes.getAndIncrement(); - if ((value % 50000) == 0) { - log.warn("broke role is slave, so putMessage is forbidden"); - } - return PutMessageStatus.SERVICE_NOT_AVAILABLE; + public long getMajorFileSize() { + long commitLogSize = 0; + if (this.commitLog != null) { + commitLogSize = this.commitLog.getTotalSize(); } - if (!this.runningFlags.isWriteable()) { - long value = this.printTimes.getAndIncrement(); - if ((value % 50000) == 0) { - log.warn("the message store is not writable. It may be caused by one of the following reasons: " + - "the broker's disk is full, write to logic queue error, write to index file error, etc"); - } - return PutMessageStatus.SERVICE_NOT_AVAILABLE; - } else { - this.printTimes.set(0); + long consumeQueueSize = 0; + if (this.consumeQueueStore != null) { + consumeQueueSize = this.consumeQueueStore.getTotalSize(); } - if (this.isOSPageCacheBusy()) { - return PutMessageStatus.OS_PAGECACHE_BUSY; + long indexFileSize = 0; + if (this.indexService != null) { + indexFileSize = this.indexService.getTotalSize(); } - return PutMessageStatus.PUT_OK; - } - private PutMessageStatus checkLmqMessage(MessageExtBrokerInner msg) { - if (msg.getProperties() != null - && StringUtils.isNotBlank(msg.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH)) - && this.isLmqConsumeQueueNumExceeded()) { - return PutMessageStatus.LMQ_CONSUME_QUEUE_NUM_EXCEEDED; - } - return PutMessageStatus.PUT_OK; + return commitLogSize + consumeQueueSize + indexFileSize; } - private boolean isLmqConsumeQueueNumExceeded() { - if (this.getMessageStoreConfig().isEnableLmq() && this.getMessageStoreConfig().isEnableMultiDispatch() - && this.lmqConsumeQueueNum.get() > this.messageStoreConfig.getMaxLmqConsumeQueueNum()) { - return true; - } - return false; + @Override + public void destroyLogics() { + this.consumeQueueStore.destroy(); } @Override public CompletableFuture asyncPutMessage(MessageExtBrokerInner msg) { - PutMessageStatus checkStoreStatus = this.checkStoreStatus(); - if (checkStoreStatus != PutMessageStatus.PUT_OK) { - return CompletableFuture.completedFuture(new PutMessageResult(checkStoreStatus, null)); - } - PutMessageStatus msgCheckStatus = this.checkMessage(msg); - if (msgCheckStatus == PutMessageStatus.MESSAGE_ILLEGAL) { - return CompletableFuture.completedFuture(new PutMessageResult(msgCheckStatus, null)); + for (PutMessageHook putMessageHook : putMessageHookList) { + PutMessageResult handleResult = putMessageHook.executeBeforePutMessage(msg); + if (handleResult != null) { + return CompletableFuture.completedFuture(handleResult); + } } - PutMessageStatus lmqMsgCheckStatus = this.checkLmqMessage(msg); - if (msgCheckStatus == PutMessageStatus.LMQ_CONSUME_QUEUE_NUM_EXCEEDED) { - return CompletableFuture.completedFuture(new PutMessageResult(lmqMsgCheckStatus, null)); + 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) -> { + putResultFuture.thenAccept(result -> { long elapsedTime = this.getSystemClock().now() - beginTime; if (elapsedTime > 500) { - log.warn("putMessage not in lock elapsed time(ms)={}, bodyLength={}", elapsedTime, msg.getBody().length); + LOGGER.warn("DefaultMessageStore#putMessage: CommitLog#putMessage cost {}ms, topic={}, bodyLength={}", + elapsedTime, msg.getTopic(), msg.getBody().length); } this.storeStatsService.setPutMessageEntireTimeMax(elapsedTime); @@ -478,34 +587,33 @@ public CompletableFuture asyncPutMessage(MessageExtBrokerInner return putResultFuture; } + + @Override public CompletableFuture asyncPutMessages(MessageExtBatch messageExtBatch) { - PutMessageStatus checkStoreStatus = this.checkStoreStatus(); - if (checkStoreStatus != PutMessageStatus.PUT_OK) { - return CompletableFuture.completedFuture(new PutMessageResult(checkStoreStatus, null)); - } - PutMessageStatus msgCheckStatus = this.checkMessages(messageExtBatch); - if (msgCheckStatus == PutMessageStatus.MESSAGE_ILLEGAL) { - return CompletableFuture.completedFuture(new PutMessageResult(msgCheckStatus, null)); + for (PutMessageHook putMessageHook : putMessageHookList) { + PutMessageResult handleResult = putMessageHook.executeBeforePutMessage(messageExtBatch); + if (handleResult != null) { + return CompletableFuture.completedFuture(handleResult); + } } long beginTime = this.getSystemClock().now(); - CompletableFuture resultFuture = this.commitLog.asyncPutMessages(messageExtBatch); + CompletableFuture putResultFuture = this.commitLog.asyncPutMessages(messageExtBatch); - resultFuture.thenAccept((result) -> { - long elapsedTime = this.getSystemClock().now() - beginTime; - if (elapsedTime > 500) { - log.warn("not in lock elapsed time(ms)={}, bodyLength={}", elapsedTime, messageExtBatch.getBody().length); + 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(elapsedTime); + this.storeStatsService.setPutMessageEntireTimeMax(eclipseTime); if (null == result || !result.isOk()) { this.storeStatsService.getPutMessageFailedTimes().add(1); } }); - return resultFuture; + return putResultFuture; } @Override @@ -521,15 +629,15 @@ public PutMessageResult putMessages(MessageExtBatch messageExtBatch) { private PutMessageResult waitForPutResult(CompletableFuture putMessageResultFuture) { try { int putMessageTimeout = - Math.max(this.messageStoreConfig.getSyncFlushTimeout(), - this.messageStoreConfig.getSlaveTimeout()) + 5000; + 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) { - log.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."); + 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); } } @@ -548,31 +656,129 @@ 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) { + + 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 commitLog + this.commitLog.truncateDirtyFiles(offsetToTruncate); + + // truncate consume queue + this.truncateDirtyLogicFiles(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 boolean truncateFiles(long offsetToTruncate) { + 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 MessageFilter messageFilter) { + final int maxMsgNums, final int maxTotalMsgSize, final MessageFilter messageFilter) { if (this.shutdown) { - log.warn("message store has shutdown, so getMessage is forbidden"); + LOGGER.warn("message store has shutdown, so getMessage is forbidden"); return null; } if (!this.runningFlags.isReadable()) { - log.warn("message store is not readable, so getMessage is forbidden " + this.runningFlags.getFlagBits()); + LOGGER.warn("message store is not readable, so getMessage is forbidden " + this.runningFlags.getFlagBits()); return null; } - if (MixAll.isLmq(topic) && this.isLmqConsumeQueueNumExceeded()) { - log.warn("message store is not available, broker config enableLmq and enableMultiDispatch, lmq consumeQueue num exceed maxLmqConsumeQueueNum config num"); - 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(); @@ -581,12 +787,11 @@ public GetMessageResult getMessage(final String group, final String topic, final long minOffset = 0; long maxOffset = 0; - // lazy init when find msg. - GetMessageResult getResult = null; + GetMessageResult getResult = new GetMessageResult(); final long maxOffsetPy = this.commitLog.getMaxOffset(); - ConsumeQueue consumeQueue = findConsumeQueue(topic, queueId); + ConsumeQueueInterface consumeQueue = findConsumeQueue(topic, queueId); if (consumeQueue != null) { minOffset = consumeQueue.getMinOffsetInQueue(); maxOffset = consumeQueue.getMaxOffsetInQueue(); @@ -602,61 +807,68 @@ public GetMessageResult getMessage(final String group, final String topic, final 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); - } + nextBeginOffset = nextOffsetCorrection(offset, maxOffset); } else { - SelectMappedBufferResult bufferConsumeQueue = consumeQueue.getIndexBuffer(offset); - if (bufferConsumeQueue != null) { - try { - status = GetMessageStatus.NO_MATCHED_MESSAGE; - - long nextPhyFileStartOffset = Long.MIN_VALUE; - long maxPhyOffsetPulling = 0; + final int maxFilterMessageSize = Math.max(16000, maxMsgNums * consumeQueue.getUnitSize()); + final boolean diskFallRecorded = this.messageStoreConfig.isDiskFallRecorded(); - int i = 0; - final int maxFilterMessageCount = Math.max(16000, maxMsgNums * ConsumeQueue.CQ_STORE_UNIT_SIZE); - final boolean diskFallRecorded = this.messageStoreConfig.isDiskFallRecorded(); - - getResult = new GetMessageResult(maxMsgNums); + 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 = consumeQueue.iterateFrom(nextBeginOffset); + + if (bufferConsumeQueue == null) { + status = GetMessageStatus.OFFSET_FOUND_NULL; + nextBeginOffset = nextOffsetCorrection(nextBeginOffset, this.consumeQueueStore.rollNextFile(consumeQueue, nextBeginOffset)); + LOGGER.warn("consumer request topic: " + topic + "offset: " + offset + " minOffset: " + minOffset + " maxOffset: " + + maxOffset + ", but access logic queue failed. Correct nextBeginOffset to " + nextBeginOffset); + break; + } - ConsumeQueueExt.CqExtUnit cqExtUnit = new ConsumeQueueExt.CqExtUnit(); - for (; i < bufferConsumeQueue.getSize() && i < maxFilterMessageCount; i += ConsumeQueue.CQ_STORE_UNIT_SIZE) { - long offsetPy = bufferConsumeQueue.getByteBuffer().getLong(); - int sizePy = bufferConsumeQueue.getByteBuffer().getInt(); - long tagsCode = bufferConsumeQueue.getByteBuffer().getLong(); + try { + long nextPhyFileStartOffset = Long.MIN_VALUE; + while (bufferConsumeQueue.hasNext() + && nextBeginOffset < maxOffset) { + CqUnit cqUnit = bufferConsumeQueue.next(); + long offsetPy = cqUnit.getPos(); + int sizePy = cqUnit.getSize(); - maxPhyOffsetPulling = offsetPy; + boolean isInMem = estimateInMemByCommitOffset(offsetPy, maxOffsetPy); - if (nextPhyFileStartOffset != Long.MIN_VALUE) { - if (offsetPy < nextPhyFileStartOffset) - continue; + if ((cqUnit.getQueueOffset() - offset) * consumeQueue.getUnitSize() > maxFilterMessageSize) { + break; } - boolean isInDisk = checkInDiskByCommitOffset(offsetPy, maxOffsetPy); + if (this.isTheBatchFull(sizePy, cqUnit.getBatchNum(), maxMsgNums, maxPullSize, getResult.getBufferTotalSize(), getResult.getMessageCount(), isInMem)) { + break; + } - if (this.isTheBatchFull(sizePy, maxMsgNums, getResult.getBufferTotalSize(), getResult.getMessageCount(), - isInDisk)) { + if (getResult.getBufferTotalSize() >= maxPullSize) { break; } - boolean extRet = false, isTagsCodeLegal = true; - if (consumeQueue.isExtAddr(tagsCode)) { - extRet = consumeQueue.getExt(tagsCode, cqExtUnit); - if (extRet) { - tagsCode = cqExtUnit.getTagsCode(); - } 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={}, group={}", - tagsCode, offsetPy, sizePy, topic, group); - isTagsCodeLegal = false; + 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(isTagsCodeLegal ? tagsCode : null, extRet ? cqExtUnit : null)) { + && !messageFilter.isMatchedByConsumeQueue(cqUnit.getValidTagsCodeAsLong(), cqUnit.getCqExtUnit())) { if (getResult.getBufferTotalSize() == 0) { status = GetMessageStatus.NO_MATCHED_MESSAGE; } @@ -674,6 +886,10 @@ public GetMessageResult getMessage(final String group, final String topic, final continue; } + if (messageStoreConfig.isColdDataFlowControlEnable() && !MixAll.isSysConsumerGroupForNoColdReadLimit(group) && !selectResult.isInCache()) { + getResult.setColdDataSum(getResult.getColdDataSum() + sizePy); + } + if (messageFilter != null && !messageFilter.isMatchedByCommitLog(selectResult.getByteBuffer().slice(), null)) { if (getResult.getBufferTotalSize() == 0) { @@ -683,34 +899,25 @@ public GetMessageResult getMessage(final String group, final String topic, final selectResult.release(); continue; } - - this.storeStatsService.getGetMessageTransferedMsgCount().add(1); - getResult.addMessage(selectResult); + this.storeStatsService.getGetMessageTransferredMsgCount().add(cqUnit.getBatchNum()); + getResult.addMessage(selectResult, cqUnit.getQueueOffset(), cqUnit.getBatchNum()); status = GetMessageStatus.FOUND; nextPhyFileStartOffset = Long.MIN_VALUE; } - - if (diskFallRecorded) { - long fallBehind = maxOffsetPy - maxPhyOffsetPulling; - brokerStatsManager.recordDiskFallBehindSize(group, topic, queueId, fallBehind); - } - - nextBeginOffset = offset + (i / ConsumeQueue.CQ_STORE_UNIT_SIZE); - - long diff = maxOffsetPy - maxPhyOffsetPulling; - long memory = (long) (StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE - * (this.messageStoreConfig.getAccessMessageInMemoryMaxRatio() / 100.0)); - getResult.setSuggestPullingFromSlave(diff > memory); } finally { - bufferConsumeQueue.release(); } - } else { - status = GetMessageStatus.OFFSET_FOUND_NULL; - nextBeginOffset = nextOffsetCorrection(offset, consumeQueue.rollNextFile(offset)); - log.warn("consumer request topic: " + topic + "offset: " + offset + " minOffset: " + minOffset + " maxOffset: " - + maxOffset + ", but access logic queue failed."); } + + 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; @@ -737,18 +944,37 @@ public GetMessageResult getMessage(final String group, final String topic, final 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) { - ConsumeQueue logic = this.findConsumeQueue(topic, queueId); - if (logic != null) { - long offset = logic.getMaxOffsetInQueue(); - return offset; + return getMaxOffsetInQueue(topic, queueId, true); + } + + @Override + public long getMaxOffsetInQueue(String topic, int queueId, boolean committed) { + if (committed) { + ConsumeQueueInterface logic = this.findConsumeQueue(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) { - ConsumeQueue logic = this.findConsumeQueue(topic, queueId); + ConsumeQueueInterface logic = this.findConsumeQueue(topic, queueId); if (logic != null) { return logic.getMinOffsetInQueue(); } @@ -756,15 +982,28 @@ public long getMinOffsetInQueue(String topic, int queueId) { return -1; } + @Override + public TimerMessageStore getTimerMessageStore() { + return this.timerMessageStore; + } + + @Override + public void setTimerMessageStore(TimerMessageStore timerMessageStore) { + this.timerMessageStore = timerMessageStore; + } + @Override public long getCommitLogOffsetInQueue(String topic, int queueId, long consumeQueueOffset) { - ConsumeQueue consumeQueue = findConsumeQueue(topic, queueId); + ConsumeQueueInterface consumeQueue = findConsumeQueue(topic, queueId); if (consumeQueue != null) { - SelectMappedBufferResult bufferConsumeQueue = consumeQueue.getIndexBuffer(consumeQueueOffset); + + ReferredIterator bufferConsumeQueue = consumeQueue.iterateFrom(consumeQueueOffset); if (bufferConsumeQueue != null) { try { - long offsetPy = bufferConsumeQueue.getByteBuffer().getLong(); - return offsetPy; + if (bufferConsumeQueue.hasNext()) { + long offsetPy = bufferConsumeQueue.next().getPos(); + return offsetPy; + } } finally { bufferConsumeQueue.release(); } @@ -774,15 +1013,21 @@ public long getCommitLogOffsetInQueue(String topic, int queueId, long consumeQue return 0; } + @Override public long getOffsetInQueueByTime(String topic, int queueId, long timestamp) { - ConsumeQueue logic = this.findConsumeQueue(topic, queueId); + ConsumeQueueInterface logic = this.findConsumeQueue(topic, queueId); if (logic != null) { - return logic.getOffsetInQueueByTime(timestamp); + long resultOffset = logic.getOffsetInQueueByTime(timestamp); + // 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; } + @Override public MessageExt lookMessageByOffset(long commitLogOffset) { SelectMappedBufferResult sbr = this.commitLog.getMessage(commitLogOffset, 4); if (null != sbr) { @@ -819,6 +1064,7 @@ public SelectMappedBufferResult selectOneMessageByOffset(long commitLogOffset, i return this.commitLog.getMessage(commitLogOffset, msgSize); } + @Override public String getRunningDataInfo() { return this.storeStatsService.toString(); } @@ -826,7 +1072,7 @@ public String getRunningDataInfo() { public String getStorePathPhysic() { String storePathPhysic; if (DefaultMessageStore.this.getMessageStoreConfig().isEnableDLegerCommitLog()) { - storePathPhysic = ((DLedgerCommitLog)DefaultMessageStore.this.getCommitLog()).getdLedgerServer().getdLedgerConfig().getDataStorePath(); + storePathPhysic = ((DLedgerCommitLog) DefaultMessageStore.this.getCommitLog()).getdLedgerServer().getdLedgerConfig().getDataStorePath(); } else { storePathPhysic = DefaultMessageStore.this.getMessageStoreConfig().getStorePathCommitLog(); } @@ -844,10 +1090,10 @@ public HashMap getRuntimeInfo() { { double minPhysicsUsedRatio = Double.MAX_VALUE; String commitLogStorePath = getStorePathPhysic(); - String[] paths = commitLogStorePath.trim().split(MessageStoreConfig.MULTI_PATH_SPLITTER); + String[] paths = commitLogStorePath.trim().split(MixAll.MULTI_PATH_SPLITTER); for (String clPath : paths) { double physicRatio = UtilAll.isPathExists(clPath) ? - UtilAll.getDiskPartitionSpaceUsedPercent(clPath) : -1; + UtilAll.getDiskPartitionSpaceUsedPercent(clPath) : -1; result.put(RunningStats.commitLogDiskRatio.name() + "_" + clPath, String.valueOf(physicRatio)); minPhysicsUsedRatio = Math.min(minPhysicsUsedRatio, physicRatio); } @@ -859,12 +1105,6 @@ public HashMap getRuntimeInfo() { result.put(RunningStats.consumeQueueDiskRatio.name(), String.valueOf(logicsRatio)); } - { - if (this.scheduleMessageService != null) { - this.scheduleMessageService.buildRunningStats(result); - } - } - result.put(RunningStats.commitLogMinOffset.name(), String.valueOf(DefaultMessageStore.this.getMinPhyOffset())); result.put(RunningStats.commitLogMaxOffset.name(), String.valueOf(DefaultMessageStore.this.getMaxPhyOffset())); @@ -876,34 +1116,45 @@ 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) { - ConsumeQueue logicQueue = this.findConsumeQueue(topic, queueId); + ConsumeQueueInterface logicQueue = this.findConsumeQueue(topic, queueId); if (logicQueue != null) { - long minLogicOffset = logicQueue.getMinLogicOffset(); - - SelectMappedBufferResult result = logicQueue.getIndexBuffer(minLogicOffset / ConsumeQueue.CQ_STORE_UNIT_SIZE); - return getStoreTime(result); + return getStoreTime(logicQueue.getEarliestUnit()); } return -1; } - private long getStoreTime(SelectMappedBufferResult result) { + @Override + public CompletableFuture getEarliestMessageTimeAsync(String topic, int queueId) { + return CompletableFuture.completedFuture(getEarliestMessageTime(topic, queueId)); + } + + protected long getStoreTime(CqUnit result) { if (result != null) { try { - final long phyOffset = result.getByteBuffer().getLong(); - final int size = result.getByteBuffer().getInt(); + final long phyOffset = result.getPos(); + final int size = result.getSize(); long storeTime = this.getCommitLog().pickupStoreTimestamp(phyOffset, size); return storeTime; } catch (Exception e) { - } finally { - result.release(); } } return -1; @@ -911,25 +1162,32 @@ private long getStoreTime(SelectMappedBufferResult result) { @Override public long getEarliestMessageTime() { - final long minPhyOffset = this.getMinPhyOffset(); - final int size = this.messageStoreConfig.getMaxMessageSize() * 2; + long minPhyOffset = this.getMinPhyOffset(); + if (this.getCommitLog() instanceof DLedgerCommitLog) { + minPhyOffset += DLedgerEntry.BODY_OFFSET; + } + final int size = MessageDecoder.MESSAGE_STORE_TIMESTAMP_POSITION + 8; return this.getCommitLog().pickupStoreTimestamp(minPhyOffset, size); } @Override public long getMessageStoreTimeStamp(String topic, int queueId, long consumeQueueOffset) { - ConsumeQueue logicQueue = this.findConsumeQueue(topic, queueId); + ConsumeQueueInterface logicQueue = this.findConsumeQueue(topic, queueId); if (logicQueue != null) { - SelectMappedBufferResult result = logicQueue.getIndexBuffer(consumeQueueOffset); - return getStoreTime(result); + return getStoreTime(logicQueue.get(consumeQueueOffset)); } 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) { - ConsumeQueue logicQueue = this.findConsumeQueue(topic, queueId); + ConsumeQueueInterface logicQueue = this.findConsumeQueue(topic, queueId); if (logicQueue != null) { return logicQueue.getMessageTotalInQueue(); } @@ -940,17 +1198,27 @@ public long getMessageTotalInQueue(String topic, int queueId) { @Override public SelectMappedBufferResult getCommitLogData(final long offset) { if (this.shutdown) { - log.warn("message store has shutdown, so getPhyQueueData is forbidden"); + 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) { - log.warn("message store has shutdown, so appendToPhyQueue is forbidden"); + LOGGER.warn("message store has shutdown, so appendToCommitLog is forbidden"); return false; } @@ -958,7 +1226,9 @@ public boolean appendToCommitLog(long startOffset, byte[] data, int dataStart, i if (result) { this.reputMessageService.wakeup(); } else { - log.error("appendToPhyQueue failed " + startOffset + " " + data.length); + LOGGER.error( + "DefaultMessageStore#appendToCommitLog: failed to append data to commitLog, physical offset={}, data " + + "length={}", startOffset, data.length); } return result; @@ -990,36 +1260,20 @@ public QueryMessageResult queryMessage(String topic, String key, int maxNum, lon long offset = queryOffsetResult.getPhyOffsets().get(m); try { - - boolean match = true; MessageExt msg = this.lookMessageByOffset(offset); if (0 == m) { lastQueryMsgTime = msg.getStoreTimestamp(); } -// String[] keyArray = msg.getKeys().split(MessageConst.KEY_SEPARATOR); -// if (topic.equals(msg.getTopic())) { -// for (String k : keyArray) { -// if (k.equals(key)) { -// match = true; -// break; -// } -// } -// } - - if (match) { - 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); - } - } else { - log.warn("queryMessage hash duplicate, {} {}", topic, key); + 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) { - log.error("queryMessage exception", e); + LOGGER.error("queryMessage exception", e); } } @@ -1035,14 +1289,54 @@ public QueryMessageResult queryMessage(String topic, String key, int maxNum, lon 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 void updateHaMasterAddress(String newAddr) { - this.haService.updateMasterAddress(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() { - return this.commitLog.getMaxOffset() - this.haService.getPush2SlaveMaxOffset().get(); + 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 @@ -1050,91 +1344,89 @@ 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 cleanUnusedTopic(Set topics) { - Iterator>> it = this.consumeQueueTable.entrySet().iterator(); - while (it.hasNext()) { - Entry> next = it.next(); - String topic = next.getKey(); + public int deleteTopics(final Set deleteTopics) { + if (deleteTopics == null || deleteTopics.isEmpty()) { + return 0; + } - if (!topics.contains(topic) && !topic.equals(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC) - && !topic.equals(TopicValidator.RMQ_SYS_TRANS_OP_HALF_TOPIC) - && !MixAll.isLmq(topic)) { - ConcurrentMap queueTable = next.getValue(); - for (ConsumeQueue cq : queueTable.values()) { - cq.destroy(); - log.info("cleanUnusedTopic: {} {} ConsumeQueue cleaned", - cq.getTopic(), - cq.getQueueId() - ); + int deleteCount = 0; + for (String topic : deleteTopics) { + ConcurrentMap queueTable = + this.consumeQueueStore.getConsumeQueueTable().get(topic); - this.commitLog.removeQueueFromTopicQueueTable(cq.getTopic(), cq.getQueueId()); - } - it.remove(); + if (queueTable == null || queueTable.isEmpty()) { + continue; + } - if (this.brokerConfig.isAutoDeleteUnusedStats()) { - this.brokerStatsManager.onTopicDeleted(topic); - } + for (ConsumeQueueInterface cq : queueTable.values()) { + this.consumeQueueStore.destroy(cq); + LOGGER.info("DeleteTopic: ConsumeQueue has been cleaned, topic={}, queueId={}", + cq.getTopic(), cq.getQueueId()); + this.consumeQueueStore.removeTopicQueueTable(cq.getTopic(), cq.getQueueId()); + } + + // remove topic from cq table + this.consumeQueueStore.getConsumeQueueTable().remove(topic); - log.info("cleanUnusedTopic: {},topic destroyed", topic); + if (this.brokerConfig.isAutoDeleteUnusedStats()) { + 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; + } - return 0; + @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(); - Iterator>> it = this.consumeQueueTable.entrySet().iterator(); - while (it.hasNext()) { - Entry> next = it.next(); - String topic = next.getKey(); - if (!topic.equals(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC)) { - ConcurrentMap queueTable = next.getValue(); - Iterator> itQT = queueTable.entrySet().iterator(); - while (itQT.hasNext()) { - 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); - - DefaultMessageStore.this.commitLog.removeQueueFromTopicQueueTable(nextQT.getValue().getTopic(), - nextQT.getValue().getQueueId()); - - nextQT.getValue().destroy(); - itQT.remove(); - } - } - - if (queueTable.isEmpty()) { - log.info("cleanExpiredConsumerQueue: {},topic destroyed", topic); - it.remove(); - } - } - } + this.consumeQueueStore.cleanExpired(minCommitLogOffset); } public Map getMessageIds(final String topic, final int queueId, long minOffset, long maxOffset, SocketAddress storeHost) { - Map messageIds = new HashMap(); + Map messageIds = new HashMap<>(); if (this.shutdown) { return messageIds; } - ConsumeQueue consumeQueue = findConsumeQueue(topic, queueId); + ConsumeQueueInterface consumeQueue = findConsumeQueue(topic, queueId); if (consumeQueue != null) { minOffset = Math.max(minOffset, consumeQueue.getMinOffsetInQueue()); maxOffset = Math.min(maxOffset, consumeQueue.getMaxOffsetInQueue()); @@ -1145,28 +1437,30 @@ public Map getMessageIds(final String topic, final int queueId, lo long nextOffset = minOffset; while (nextOffset < maxOffset) { - SelectMappedBufferResult bufferConsumeQueue = consumeQueue.getIndexBuffer(nextOffset); - if (bufferConsumeQueue != null) { - try { - int i = 0; - for (; i < bufferConsumeQueue.getSize(); i += ConsumeQueue.CQ_STORE_UNIT_SIZE) { - long offsetPy = bufferConsumeQueue.getByteBuffer().getLong(); + 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, nextOffset++); - if (nextOffset > maxOffset) { + messageIds.put(msgId, cqUnit.getQueueOffset()); + nextOffset = cqUnit.getQueueOffset() + cqUnit.getBatchNum(); + if (nextOffset >= maxOffset) { return messageIds; } } - } finally { - + } else { + return messageIds; + } + } finally { + if (bufferConsumeQueue != null) { bufferConsumeQueue.release(); } - } else { - return messageIds; } } } @@ -1174,24 +1468,18 @@ public Map getMessageIds(final String topic, final int queueId, lo } @Override + @Deprecated public boolean checkInDiskByConsumeOffset(final String topic, final int queueId, long consumeOffset) { final long maxOffsetPy = this.commitLog.getMaxOffset(); - ConsumeQueue consumeQueue = findConsumeQueue(topic, queueId); + ConsumeQueueInterface consumeQueue = findConsumeQueue(topic, queueId); if (consumeQueue != null) { - SelectMappedBufferResult bufferConsumeQueue = consumeQueue.getIndexBuffer(consumeOffset); - if (bufferConsumeQueue != null) { - try { - for (int i = 0; i < bufferConsumeQueue.getSize(); ) { - i += ConsumeQueue.CQ_STORE_UNIT_SIZE; - long offsetPy = bufferConsumeQueue.getByteBuffer().getLong(); - return checkInDiskByCommitOffset(offsetPy, maxOffsetPy); - } - } finally { + CqUnit cqUnit = consumeQueue.get(consumeOffset); - bufferConsumeQueue.release(); - } + if (cqUnit != null) { + long offsetPy = cqUnit.getPos(); + return !estimateInMemByCommitOffset(offsetPy, maxOffsetPy); } else { return false; } @@ -1199,31 +1487,180 @@ public boolean checkInDiskByConsumeOffset(final String topic, final int queueId, return false; } + @Override + public boolean checkInMemByConsumeOffset(final String topic, final int queueId, long consumeOffset, int batchSize) { + ConsumeQueueInterface consumeQueue = findConsumeQueue(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(); } + public long flushBehindBytes() { + return this.commitLog.remainHowManyDataToCommit() + this.commitLog.remainHowManyDataToFlush(); + } + @Override public long flush() { return this.commitLog.flush(); } + @Override + public long getFlushedWhere() { + return this.commitLog.getFlushedWhere(); + } + @Override public boolean resetWriteOffset(long phyOffset) { - return this.commitLog.resetOffset(phyOffset); + //copy a new map + ConcurrentHashMap newMap = new ConcurrentHashMap<>(consumeQueueStore.getTopicQueueTable()); + SelectMappedBufferResult lastBuffer = null; + long startReadOffset = phyOffset == -1 ? 0 : phyOffset; + while ((lastBuffer = 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 = 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(); + Long cur = newMap.get(key); + if (cur != null && cur > msg.getQueueOffset()) { + newMap.put(key, msg.getQueueOffset()); + } + startReadOffset += msg.getStoreSize(); + } catch (Throwable e) { + LOGGER.error("resetWriteOffset error.", e); + } finally { + if (lastBuffer != null) + lastBuffer.release(); + } + } + if (this.commitLog.resetOffset(phyOffset)) { + this.consumeQueueStore.setTopicQueueTable(newMap); + return true; + } else { + return false; + } } + // 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) { @@ -1237,88 +1674,85 @@ public MessageExt lookMessageByOffset(long commitLogOffset, int size) { return null; } - public ConsumeQueue findConsumeQueue(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; - } - } - - ConsumeQueue logic = map.get(queueId); - if (null == logic) { - ConsumeQueue newLogic = new ConsumeQueue( - topic, - queueId, - StorePathConfigHelper.getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()), - this.getMessageStoreConfig().getMappedFileSizeConsumeQueue(), - this); - ConsumeQueue oldLogic = map.putIfAbsent(queueId, newLogic); - if (oldLogic != null) { - logic = oldLogic; - } else { - if (MixAll.isLmq(topic)) { - lmqConsumeQueueNum.getAndIncrement(); - } - logic = newLogic; - } - } - - return logic; + @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()) { + if (this.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE || + this.getMessageStoreConfig().isOffsetCheckInSlave()) { nextOffset = newOffset; } return nextOffset; } - private boolean checkInDiskByCommitOffset(long offsetPy, long maxOffsetPy) { + 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 maxMsgNums, int bufferTotal, int messageTotal, boolean isInDisk) { + 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 (maxMsgNums <= messageTotal) { + if (messageTotal + unitBatchNum > maxMsgNums) { return true; } - if (isInDisk) { - if ((bufferTotal + sizePy) > this.messageStoreConfig.getMaxTransferBytesOnMessageInDisk()) { - return true; - } + if (bufferTotal + sizePy > maxMsgSize) { + return true; + } - if (messageTotal > this.messageStoreConfig.getMaxTransferCountOnMessageInDisk() - 1) { - return true; - } - } else { + if (isInMem) { if ((bufferTotal + sizePy) > this.messageStoreConfig.getMaxTransferBytesOnMessageInMemory()) { return true; } - if (messageTotal > this.messageStoreConfig.getMaxTransferCountOnMessageInMemory() - 1) { + return messageTotal > this.messageStoreConfig.getMaxTransferCountOnMessageInMemory() - 1; + } else { + if ((bufferTotal + sizePy) > this.messageStoreConfig.getMaxTransferBytesOnMessageInDisk()) { return true; } - } - return false; + return messageTotal > this.messageStoreConfig.getMaxTransferCountOnMessageInDisk() - 1; + } } private void deleteFile(final String fileName) { File file = new File(fileName); boolean result = file.delete(); - log.info(fileName + (result ? " delete OK" : " delete Failed")); + LOGGER.info(fileName + (result ? " delete OK" : " delete Failed")); } /** @@ -1327,30 +1761,31 @@ private void deleteFile(final String fileName) { private void createTempFile() throws IOException { String fileName = StorePathConfigHelper.getAbortFile(this.messageStoreConfig.getStorePathRootDir()); File file = new File(fileName); - MappedFile.ensureDirOK(file.getParent()); + UtilAll.ensureDirOK(file.getParent()); boolean result = file.createNewFile(); - log.info(fileName + (result ? " create OK" : " already exists")); + LOGGER.info(fileName + (result ? " create OK" : " already exists")); + MixAll.string2File(Long.toString(MixAll.getPID()), file.getAbsolutePath()); } private void addScheduleTask() { - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + this.scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) { @Override - public void run() { + public void run0() { DefaultMessageStore.this.cleanFilesPeriodically(); } }, 1000 * 60, this.messageStoreConfig.getCleanResourceInterval(), TimeUnit.MILLISECONDS); - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + this.scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) { @Override - public void run() { + public void run0() { DefaultMessageStore.this.checkSelf(); } }, 1, 10, TimeUnit.MINUTES); - this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + this.scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) { @Override - public void run() { + public void run0() { if (DefaultMessageStore.this.getMessageStoreConfig().isDebugLockEnable()) { try { if (DefaultMessageStore.this.commitLog.getBeginTimeInLock() != 0) { @@ -1369,36 +1804,41 @@ public void run() { } }, 1, 1, TimeUnit.SECONDS); + this.scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) { + @Override + public void run0() { + DefaultMessageStore.this.storeCheckpoint.flush(); + } + }, 1, 1, TimeUnit.SECONDS); + + this.scheduledCleanQueueExecutorService.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + DefaultMessageStore.this.cleanQueueFilesPeriodically(); + } + }, 1000 * 60, this.messageStoreConfig.getCleanResourceInterval(), TimeUnit.MILLISECONDS); + + // this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { // @Override // public void run() { // DefaultMessageStore.this.cleanExpiredConsumerQueue(); // } // }, 1, 1, TimeUnit.HOURS); - this.diskCheckScheduledExecutorService.scheduleAtFixedRate(new Runnable() { - public void run() { - DefaultMessageStore.this.cleanCommitLogService.isSpaceFull(); - } - }, 1000L, 10000L, TimeUnit.MILLISECONDS); } private void cleanFilesPeriodically() { this.cleanCommitLogService.run(); + } + + private void cleanQueueFilesPeriodically() { + this.correctLogicOffsetService.run(); this.cleanConsumeQueueService.run(); } private void checkSelf() { this.commitLog.checkSelf(); - - Iterator>> it = this.consumeQueueTable.entrySet().iterator(); - while (it.hasNext()) { - Entry> next = it.next(); - Iterator> itNext = next.getValue().entrySet().iterator(); - while (itNext.hasNext()) { - Entry cq = itNext.next(); - cq.getValue().checkSelf(); - } - } + this.consumeQueueStore.checkSelf(); } private boolean isTempFileExist() { @@ -1407,109 +1847,76 @@ private boolean isTempFileExist() { return file.exists(); } - private boolean loadConsumeQueue() { - File dirLogic = new File(StorePathConfigHelper.getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir())); - 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; - } - ConsumeQueue logic = new ConsumeQueue( - topic, - queueId, - StorePathConfigHelper.getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()), - this.getMessageStoreConfig().getMappedFileSizeConsumeQueue(), - this); - this.putConsumeQueue(topic, queueId, logic); - if (!logic.load()) { - return false; - } - } - } - } - } - - log.info("load logics queue all over, OK"); - - return true; - } - private void recover(final boolean lastExitOK) { - long maxPhyOffsetOfConsumeQueue = this.recoverConsumeQueue(); + boolean recoverConcurrently = this.brokerConfig.isRecoverConcurrently(); + LOGGER.info("message store recover mode: {}", recoverConcurrently ? "concurrent" : "normal"); + // recover consume queue + long recoverConsumeQueueStart = System.currentTimeMillis(); + this.recoverConsumeQueue(); + long maxPhyOffsetOfConsumeQueue = this.getMaxOffsetInConsumeQueue(); + long recoverConsumeQueueEnd = System.currentTimeMillis(); + + // recover commitlog if (lastExitOK) { this.commitLog.recoverNormally(maxPhyOffsetOfConsumeQueue); } else { this.commitLog.recoverAbnormally(maxPhyOffsetOfConsumeQueue); } + // recover consume offset table + long recoverCommitLogEnd = System.currentTimeMillis(); this.recoverTopicQueueTable(); + long recoverConsumeOffsetEnd = System.currentTimeMillis(); + + LOGGER.info("message store recover total cost: {} ms, " + + "recoverConsumeQueue: {} ms, recoverCommitLog: {} ms, recoverOffsetTable: {} ms", + recoverConsumeOffsetEnd - recoverConsumeQueueStart, recoverConsumeQueueEnd - recoverConsumeQueueStart, + recoverCommitLogEnd - recoverConsumeQueueEnd, recoverConsumeOffsetEnd - recoverCommitLogEnd); } + @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; } - private void putConsumeQueue(final String topic, final int queueId, final ConsumeQueue 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)) { - this.lmqConsumeQueueNum.getAndIncrement(); - } + private void recoverConsumeQueue() { + if (!this.brokerConfig.isRecoverConcurrently()) { + this.consumeQueueStore.recover(); } else { - map.put(queueId, consumeQueue); + this.consumeQueueStore.recoverConcurrently(); } } - private long recoverConsumeQueue() { - long maxPhysicOffset = -1; - for (ConcurrentMap maps : this.consumeQueueTable.values()) { - for (ConsumeQueue logic : maps.values()) { - logic.recover(); - if (logic.getMaxPhysicOffset() > maxPhysicOffset) { - maxPhysicOffset = logic.getMaxPhysicOffset(); - } - } - } - - return maxPhysicOffset; + private long getMaxOffsetInConsumeQueue() { + return this.consumeQueueStore.getMaxOffsetInConsumeQueue(); } public void recoverTopicQueueTable() { - HashMap table = new HashMap(1024); long minPhyOffset = this.commitLog.getMinOffset(); - for (ConcurrentMap maps : this.consumeQueueTable.values()) { - for (ConsumeQueue logic : maps.values()) { - String key = logic.getTopic() + "-" + logic.getQueueId(); - table.put(key, logic.getMaxOffsetInQueue()); - logic.correctMinOffset(minPhyOffset); - } - } - - this.commitLog.setTopicQueueTable(table); + this.consumeQueueStore.recoverOffsetTable(minPhyOffset); } + @Override public AllocateMappedFileService getAllocateMappedFileService() { return allocateMappedFileService; } + @Override public StoreStatsService getStoreStatsService() { return storeStatsService; } @@ -1518,22 +1925,21 @@ public RunningFlags getAccessRights() { return runningFlags; } - public ConcurrentMap> getConsumeQueueTable() { - return consumeQueueTable; + public ConcurrentMap> getConsumeQueueTable() { + return consumeQueueStore.getConsumeQueueTable(); } + @Override public StoreCheckpoint getStoreCheckpoint() { return storeCheckpoint; } + @Override public HAService getHaService() { return haService; } - public ScheduleMessageService getScheduleMessageService() { - return scheduleMessageService; - } - + @Override public RunningFlags getRunningFlags() { return runningFlags; } @@ -1545,64 +1951,30 @@ public void doDispatch(DispatchRequest req) { } public void putMessagePositionInfo(DispatchRequest dispatchRequest) { - ConsumeQueue cq = this.findConsumeQueue(dispatchRequest.getTopic(), dispatchRequest.getQueueId()); - cq.putMessagePositionInfoWrapper(dispatchRequest, checkMultiDispatchQueue(dispatchRequest)); - } - - private boolean checkMultiDispatchQueue(DispatchRequest dispatchRequest) { - if (!this.messageStoreConfig.isEnableMultiDispatch()) { - 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; + this.consumeQueueStore.putMessagePositionInfoWrapper(dispatchRequest); } @Override - public BrokerStatsManager getBrokerStatsManager() { - return brokerStatsManager; + 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 void handleScheduleMessageService(final BrokerRole brokerRole) { - if (this.scheduleMessageService != null) { - if (brokerRole == BrokerRole.SLAVE) { - this.scheduleMessageService.shutdown(); - } else { - this.scheduleMessageService.start(); - } - } + public long getStateMachineVersion() { + return stateMachineVersion; + } + public void setStateMachineVersion(long stateMachineVersion) { + this.stateMachineVersion = stateMachineVersion; } - @Override - public void cleanUnusedLmqTopic(String topic) { - if (this.consumeQueueTable.containsKey(topic)) { - ConcurrentMap map = this.consumeQueueTable.get(topic); - if (map != null) { - ConsumeQueue cq = map.get(0); - cq.destroy(); - log.info("cleanUnusedLmqTopic: {} {} ConsumeQueue cleaned", - cq.getTopic(), - cq.getQueueId() - ); + public BrokerStatsManager getBrokerStatsManager() { + return brokerStatsManager; + } - this.commitLog.removeQueueFromTopicQueueTable(cq.getTopic(), cq.getQueueId()); - this.lmqConsumeQueueNum.getAndDecrement(); - } - this.consumeQueueTable.remove(topic); - if (this.brokerConfig.isAutoDeleteUnusedStats()) { - this.brokerStatsManager.onTopicDeleted(topic); - } - log.info("cleanUnusedLmqTopic: {},topic destroyed", topic); - } + public BrokerConfig getBrokerConfig() { + return brokerConfig; } public int remainTransientStoreBufferNumbs() { @@ -1614,20 +1986,51 @@ 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 ConsumeQueue getConsumeQueue(String topic, int queueId) { - ConcurrentMap map = consumeQueueTable.get(topic); + 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) { + ConcurrentMap map = this.getConsumeQueueTable().get(topic); if (map == null) { return null; } return map.get(queueId); } + @Override public void unlockMappedFile(final MappedFile mappedFile) { this.scheduledExecutorService.schedule(new Runnable() { @Override @@ -1637,6 +2040,82 @@ public void run() { }, 6, TimeUnit.SECONDS); } + @Override + public PerfCounter.Ticks getPerfCounter() { + return perfs; + } + + @Override + public ConsumeQueueStore 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) { + 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) { + 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 @@ -1667,29 +2146,66 @@ public void dispatch(DispatchRequest request) { class CleanCommitLogService { private final static int MAX_MANUAL_DELETE_FILE_TIMES = 20; - private final double diskSpaceWarningLevelRatio = - Double.parseDouble(System.getProperty("rocketmq.broker.diskSpaceWarningLevelRatio", "0.90")); + private final String diskSpaceWarningLevelRatio = + System.getProperty("rocketmq.broker.diskSpaceWarningLevelRatio", ""); - private final double diskSpaceCleanForciblyRatio = - Double.parseDouble(System.getProperty("rocketmq.broker.diskSpaceCleanForciblyRatio", "0.85")); + private final String diskSpaceCleanForciblyRatio = + System.getProperty("rocketmq.broker.diskSpaceCleanForciblyRatio", ""); private long lastRedeleteTimestamp = 0; private volatile int manualDeleteFileSeveralTimes = 0; 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 = MAX_MANUAL_DELETE_FILE_TIMES; - DefaultMessageStore.log.info("executeDeleteFilesManually was invoked"); + DefaultMessageStore.LOGGER.info("executeDeleteFilesManually was invoked"); } public void run() { try { this.deleteExpiredFiles(); - - this.redeleteHangedFile(); + this.reDeleteHangedFile(); } catch (Throwable e) { - DefaultMessageStore.log.warn(this.getServiceName() + " service has exception. ", e); + DefaultMessageStore.LOGGER.warn(this.getServiceName() + " service has exception. ", e); } } @@ -1697,57 +2213,67 @@ private void deleteExpiredFiles() { int deleteCount = 0; long fileReservedTime = DefaultMessageStore.this.getMessageStoreConfig().getFileReservedTime(); int deletePhysicFilesInterval = DefaultMessageStore.this.getMessageStoreConfig().getDeleteCommitLogFilesInterval(); - int destroyMapedFileIntervalForcibly = DefaultMessageStore.this.getMessageStoreConfig().getDestroyMapedFileIntervalForcibly(); + int destroyMappedFileIntervalForcibly = DefaultMessageStore.this.getMessageStoreConfig().getDestroyMapedFileIntervalForcibly(); + int deleteFileBatchMax = DefaultMessageStore.this.getMessageStoreConfig().getDeleteFileBatchMax(); - boolean timeup = this.isTimeToDelete(); - boolean spacefull = this.isSpaceToDelete(); - boolean manualDelete = this.manualDeleteFileSeveralTimes > 0; + boolean isTimeUp = this.isTimeToDelete(); + boolean isUsageExceedsThreshold = this.isSpaceToDelete(); + boolean isManualDelete = this.manualDeleteFileSeveralTimes > 0; - if (timeup || spacefull || manualDelete) { + if (isTimeUp || isUsageExceedsThreshold || isManualDelete) { - if (manualDelete) + if (isManualDelete) { this.manualDeleteFileSeveralTimes--; + } boolean cleanAtOnce = DefaultMessageStore.this.getMessageStoreConfig().isCleanFileForciblyEnable() && this.cleanImmediately; - log.info("begin to delete before {} hours file. timeup: {} spacefull: {} manualDeleteFileSeveralTimes: {} cleanAtOnce: {}", + LOGGER.info("begin to delete before {} hours file. isTimeUp: {} isUsageExceedsThreshold: {} manualDeleteFileSeveralTimes: {} cleanAtOnce: {} deleteFileBatchMax: {}", fileReservedTime, - timeup, - spacefull, + isTimeUp, + isUsageExceedsThreshold, manualDeleteFileSeveralTimes, - cleanAtOnce); + cleanAtOnce, + deleteFileBatchMax); fileReservedTime *= 60 * 60 * 1000; deleteCount = DefaultMessageStore.this.commitLog.deleteExpiredFile(fileReservedTime, deletePhysicFilesInterval, - destroyMapedFileIntervalForcibly, cleanAtOnce); + destroyMappedFileIntervalForcibly, cleanAtOnce, deleteFileBatchMax); if (deleteCount > 0) { - } else if (spacefull) { - log.warn("disk space will be full soon, but delete file failed."); + // 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() { + private void reDeleteHangedFile() { int interval = DefaultMessageStore.this.getMessageStoreConfig().getRedeleteHangedFileInterval(); long currentTimestamp = System.currentTimeMillis(); if ((currentTimestamp - this.lastRedeleteTimestamp) > interval) { this.lastRedeleteTimestamp = currentTimestamp; - int destroyMapedFileIntervalForcibly = + int destroyMappedFileIntervalForcibly = DefaultMessageStore.this.getMessageStoreConfig().getDestroyMapedFileIntervalForcibly(); - if (DefaultMessageStore.this.commitLog.retryDeleteFirstFile(destroyMapedFileIntervalForcibly)) { + if (DefaultMessageStore.this.commitLog.retryDeleteFirstFile(destroyMappedFileIntervalForcibly)) { } } } public String getServiceName() { - return CleanCommitLogService.class.getSimpleName(); + return DefaultMessageStore.this.brokerConfig.getIdentifier() + CleanCommitLogService.class.getSimpleName(); } private boolean isTimeToDelete() { String when = DefaultMessageStore.this.getMessageStoreConfig().getDeleteWhen(); if (UtilAll.isItTimeToDo(when)) { - DefaultMessageStore.log.info("it's time to reclaim disk space, " + when); + DefaultMessageStore.LOGGER.info("it's time to reclaim disk space, " + when); return true; } @@ -1755,78 +2281,99 @@ private boolean isTimeToDelete() { } private boolean isSpaceToDelete() { - double ratio = DefaultMessageStore.this.getMessageStoreConfig().getDiskMaxUsedSpaceRatio() / 100.0; - cleanImmediately = false; - { - String commitLogStorePath = DefaultMessageStore.this.getStorePathPhysic(); - String[] storePaths = commitLogStorePath.trim().split(MessageStoreConfig.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 > diskSpaceCleanForciblyRatio) { - fullStorePath.add(storePathPhysic); - } + String commitLogStorePath = DefaultMessageStore.this.getMessageStoreConfig().getStorePathCommitLog(); + 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; } - DefaultMessageStore.this.commitLog.setFullStorePaths(fullStorePath); - if (minPhysicRatio > diskSpaceWarningLevelRatio) { - boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskFull(); - if (diskok) { - DefaultMessageStore.log.error("physic disk maybe full soon " + minPhysicRatio + - ", so mark disk full, storePathPhysic=" + minStorePath); - } - - cleanImmediately = true; - } else if (minPhysicRatio > diskSpaceCleanForciblyRatio) { - cleanImmediately = true; - } else { - boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskOK(); - if (!diskok) { - DefaultMessageStore.log.info("physic disk space OK " + minPhysicRatio + - ", so mark disk ok, storePathPhysic=" + minStorePath); - } + 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); } - if (minPhysicRatio < 0 || minPhysicRatio > ratio) { - DefaultMessageStore.log.info("physic disk maybe full soon, so reclaim space, " - + minPhysicRatio + ", storePathPhysic=" + minStorePath); - return true; + 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 = DefaultMessageStore.this.getStorePathLogic(); - double logicsRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathLogics); - if (logicsRatio > diskSpaceWarningLevelRatio) { - boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskFull(); - if (diskok) { - DefaultMessageStore.log.error("logics disk maybe full soon " + logicsRatio + ", so mark disk full"); - } + 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; - } else if (logicsRatio > diskSpaceCleanForciblyRatio) { - cleanImmediately = true; - } else { - boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskOK(); - if (!diskok) { - DefaultMessageStore.log.info("logics disk space OK " + logicsRatio + ", so mark disk ok"); - } + 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.log.info("logics disk maybe full soon, so reclaim space, " + logicsRatio); + 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; } - } - return false; + boolean isUsageExceedsThreshold = logicalRatio > ratio; + if (isUsageExceedsThreshold) { + DefaultMessageStore.LOGGER.info("Logical disk usage {} exceeds clean threshold {}, forcibly: {}", + logicalRatio, ratio, cleanImmediately); + } + return isUsageExceedsThreshold; + } } public int getManualDeleteFileSeveralTimes() { @@ -1840,13 +2387,13 @@ public void setManualDeleteFileSeveralTimes(int manualDeleteFileSeveralTimes) { public double calcStorePathPhysicRatio() { Set fullStorePath = new HashSet<>(); String storePath = getStorePathPhysic(); - String[] paths = storePath.trim().split(MessageStoreConfig.MULTI_PATH_SPLITTER); + 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; + UtilAll.getDiskPartitionSpaceUsedPercent(path) : -1; minPhysicRatio = Math.min(minPhysicRatio, physicRatio); - if (physicRatio > diskSpaceCleanForciblyRatio) { + if (physicRatio > getDiskSpaceCleanForciblyRatio()) { fullStorePath.add(path); } } @@ -1859,12 +2406,12 @@ public boolean isSpaceFull() { double physicRatio = calcStorePathPhysicRatio(); double ratio = DefaultMessageStore.this.getMessageStoreConfig().getDiskMaxUsedSpaceRatio() / 100.0; if (physicRatio > ratio) { - DefaultMessageStore.log.info("physic disk of commitLog used: " + physicRatio); + DefaultMessageStore.LOGGER.info("physic disk of commitLog used: " + physicRatio); } - if (physicRatio > this.diskSpaceWarningLevelRatio) { + if (physicRatio > this.getDiskSpaceWarningLevelRatio()) { boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskFull(); if (diskok) { - DefaultMessageStore.log.error("physic disk of commitLog maybe full soon, used " + physicRatio + ", so mark disk full"); + DefaultMessageStore.LOGGER.error("physic disk of commitLog maybe full soon, used " + physicRatio + ", so mark disk full"); } return true; @@ -1872,7 +2419,7 @@ public boolean isSpaceFull() { boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskOK(); if (!diskok) { - DefaultMessageStore.log.info("physic disk space of commitLog OK " + physicRatio + ", so mark disk ok"); + DefaultMessageStore.LOGGER.info("physic disk space of commitLog OK " + physicRatio + ", so mark disk ok"); } return false; @@ -1887,7 +2434,7 @@ public void run() { try { this.deleteExpiredFiles(); } catch (Throwable e) { - DefaultMessageStore.log.warn(this.getServiceName() + " service has exception. ", e); + DefaultMessageStore.LOGGER.warn(this.getServiceName() + " service has exception. ", e); } } @@ -1898,12 +2445,11 @@ private void deleteExpiredFiles() { if (minOffset > this.lastPhysicalMinOffset) { this.lastPhysicalMinOffset = minOffset; - ConcurrentMap> tables = DefaultMessageStore.this.consumeQueueTable; - - for (ConcurrentMap maps : tables.values()) { - for (ConsumeQueue logic : maps.values()) { - int deleteCount = logic.deleteExpiredFile(minOffset); + ConcurrentMap> tables = DefaultMessageStore.this.getConsumeQueueTable(); + for (ConcurrentMap maps : tables.values()) { + for (ConsumeQueueInterface logic : maps.values()) { + int deleteCount = DefaultMessageStore.this.consumeQueueStore.deleteExpiredFile(logic, minOffset); if (deleteCount > 0 && deleteLogicsFilesInterval > 0) { try { Thread.sleep(deleteLogicsFilesInterval); @@ -1918,7 +2464,127 @@ private void deleteExpiredFiles() { } public String getServiceName() { - return CleanConsumeQueueService.class.getSimpleName(); + return DefaultMessageStore.this.brokerConfig.getIdentifier() + CleanConsumeQueueService.class.getSimpleName(); + } + } + + class CorrectLogicOffsetService { + private long lastForceCorrectTime = -1L; + + public void run() { + try { + this.correctLogicMinOffset(); + } catch (Throwable e) { + LOGGER.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 (DefaultMessageStore.this.consumeQueueStore.isFirstFileExist(logic) && !DefaultMessageStore.this.consumeQueueStore.isFirstFileAvailable(logic)) { + LOGGER.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()) { + LOGGER.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 { + LOGGER.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 = DefaultMessageStore.this.getMessageStoreConfig().getCorrectLogicMinOffsetForceInterval(); + if ((System.currentTimeMillis() - lastForeCorrectTimeCurRun) > forceCorrectInterval) { + lastForceCorrectTime = System.currentTimeMillis(); + CqUnit cqUnit = logic.getEarliestUnit(); + if (cqUnit == null) { + if (logic.getMinOffsetInQueue() == logic.getMaxOffsetInQueue()) { + return false; + } else { + LOGGER.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) { + LOGGER.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 correct. + return false; + } + } + + return false; + } + + private void correctLogicMinOffset() { + + long lastForeCorrectTimeCurRun = lastForceCorrectTime; + long minPhyOffset = getMinPhyOffset(); + ConcurrentMap> tables = DefaultMessageStore.this.getConsumeQueueTable(); + for (ConcurrentMap maps : tables.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) { + DefaultMessageStore.this.consumeQueueStore.deleteExpiredFile(logic, minPhyOffset); + int sleepIntervalWhenCorrectMinOffset = DefaultMessageStore.this.getMessageStoreConfig().getCorrectLogicMinOffsetSleepInterval(); + if (sleepIntervalWhenCorrectMinOffset > 0) { + try { + Thread.sleep(sleepIntervalWhenCorrectMinOffset); + } catch (InterruptedException ignored) { + } + } + } + + public String getServiceName() { + if (brokerConfig.isInBrokerContainer()) { + return brokerConfig.getIdentifier() + CorrectLogicOffsetService.class.getSimpleName(); + } + return CorrectLogicOffsetService.class.getSimpleName(); } } @@ -1943,17 +2609,21 @@ private void doFlush(int retryTimes) { logicsMsgTimestamp = DefaultMessageStore.this.getStoreCheckpoint().getLogicsMsgTimestamp(); } - ConcurrentMap> tables = DefaultMessageStore.this.consumeQueueTable; + ConcurrentMap> tables = DefaultMessageStore.this.getConsumeQueueTable(); - for (ConcurrentMap maps : tables.values()) { - for (ConsumeQueue cq : maps.values()) { + for (ConcurrentMap maps : tables.values()) { + for (ConsumeQueueInterface cq : maps.values()) { boolean result = false; for (int i = 0; i < retryTimes && !result; i++) { - result = cq.flush(flushConsumeQueueLeastPages); + result = DefaultMessageStore.this.consumeQueueStore.flush(cq, flushConsumeQueueLeastPages); } } } + if (messageStoreConfig.isEnableCompaction()) { + compactionStore.flush(flushConsumeQueueLeastPages); + } + if (0 == flushConsumeQueueLeastPages) { if (logicsMsgTimestamp > 0) { DefaultMessageStore.this.getStoreCheckpoint().setLogicsMsgTimestamp(logicsMsgTimestamp); @@ -1962,8 +2632,9 @@ private void doFlush(int retryTimes) { } } + @Override public void run() { - DefaultMessageStore.log.info(this.getServiceName() + " service started"); + DefaultMessageStore.LOGGER.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { @@ -1971,29 +2642,100 @@ public void run() { this.waitForRunning(interval); this.doFlush(1); } catch (Exception e) { - DefaultMessageStore.log.warn(this.getServiceName() + " service has exception. ", e); + DefaultMessageStore.LOGGER.warn(this.getServiceName() + " service has exception. ", e); } } this.doFlush(RETRY_TIMES_OVER); - DefaultMessageStore.log.info(this.getServiceName() + " service end"); + DefaultMessageStore.LOGGER.info(this.getServiceName() + " service end"); } @Override public String getServiceName() { + if (DefaultMessageStore.this.brokerConfig.isInBrokerContainer()) { + return DefaultMessageStore.this.getBrokerIdentity().getIdentifier() + FlushConsumeQueueService.class.getSimpleName(); + } return FlushConsumeQueueService.class.getSimpleName(); } @Override - public long getJointime() { + public long getJoinTime() { return 1000 * 60; } } + class BatchDispatchRequest { + + private ByteBuffer byteBuffer; + + private int position; + + private int size; + + private long id; + + public BatchDispatchRequest(ByteBuffer byteBuffer, int position, int size, long id) { + this.byteBuffer = byteBuffer; + this.position = position; + this.size = size; + this.id = id; + } + } + + 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; + } + + } + class ReputMessageService extends ServiceThread { - private volatile long reputFromOffset = 0; + protected volatile long reputFromOffset = 0; public long getReputFromOffset() { return reputFromOffset; @@ -2013,101 +2755,102 @@ public void shutdown() { } if (this.isCommitLogAvailable()) { - log.warn("shutdown ReputMessageService, but commitlog have not finish to be dispatched, CL: {} reputFromOffset: {}", - DefaultMessageStore.this.commitLog.getMaxOffset(), this.reputFromOffset); + 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.commitLog.getMaxOffset() - this.reputFromOffset; + return DefaultMessageStore.this.getConfirmOffset() - this.reputFromOffset; } - private boolean isCommitLogAvailable() { - return this.reputFromOffset < DefaultMessageStore.this.commitLog.getMaxOffset(); + public boolean isCommitLogAvailable() { + return this.reputFromOffset < DefaultMessageStore.this.getConfirmOffset(); } - private void doReput() { + public void doReput() { if (this.reputFromOffset < DefaultMessageStore.this.commitLog.getMinOffset()) { - log.warn("The reputFromOffset={} is smaller than minPyOffset={}, this usually indicate that the dispatch behind too much and the commitlog has expired.", + 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; ) { - if (DefaultMessageStore.this.getMessageStoreConfig().isDuplicationEnable() - && this.reputFromOffset >= DefaultMessageStore.this.getConfirmOffset()) { + SelectMappedBufferResult result = DefaultMessageStore.this.commitLog.getData(reputFromOffset); + + if (result == null) { break; } - SelectMappedBufferResult result = DefaultMessageStore.this.commitLog.getData(reputFromOffset); - if (result != null) { - try { - this.reputFromOffset = result.getStartOffset(); - - for (int readSize = 0; readSize < result.getSize() && doNext; ) { - DispatchRequest dispatchRequest = - DefaultMessageStore.this.commitLog.checkMessageAndReturnSize(result.getByteBuffer(), false, false); - int size = dispatchRequest.getBufferSize() == -1 ? dispatchRequest.getMsgSize() : dispatchRequest.getBufferSize(); - - if (dispatchRequest.isSuccess()) { - if (size > 0) { - DefaultMessageStore.this.doDispatch(dispatchRequest); - - if (BrokerRole.SLAVE != DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole() - && 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()); - notifyMessageArrive4MultiQueue(dispatchRequest); - } - - this.reputFromOffset += size; - readSize += size; - if (DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) { - DefaultMessageStore.this.storeStatsService - .getSinglePutMessageTopicTimesTotal(dispatchRequest.getTopic()).add(1); - DefaultMessageStore.this.storeStatsService - .getSinglePutMessageTopicSizeTotal(dispatchRequest.getTopic()) - .add(dispatchRequest.getMsgSize()); - } - } else if (size == 0) { - this.reputFromOffset = DefaultMessageStore.this.commitLog.rollNextFile(this.reputFromOffset); - readSize = result.getSize(); + try { + this.reputFromOffset = result.getStartOffset(); + + for (int readSize = 0; readSize < result.getSize() && reputFromOffset < DefaultMessageStore.this.getConfirmOffset() && doNext; ) { + DispatchRequest dispatchRequest = + DefaultMessageStore.this.commitLog.checkMessageAndReturnSize(result.getByteBuffer(), false, false, false); + int size = dispatchRequest.getBufferSize() == -1 ? dispatchRequest.getMsgSize() : dispatchRequest.getBufferSize(); + + if (reputFromOffset + size > DefaultMessageStore.this.getConfirmOffset()) { + doNext = false; + break; + } + + if (dispatchRequest.isSuccess()) { + if (size > 0) { + DefaultMessageStore.this.doDispatch(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()); + notifyMessageArrive4MultiQueue(dispatchRequest); } - } else if (!dispatchRequest.isSuccess()) { - if (size > 0) { - log.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) { - log.error("[BUG]dispatch message to consume queue error, COMMITLOG OFFSET: {}", - this.reputFromOffset); - this.reputFromOffset += result.getSize() - readSize; - } + 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; } } } - } finally { - result.release(); } - } else { - doNext = false; + } finally { + result.release(); } } } private void notifyMessageArrive4MultiQueue(DispatchRequest dispatchRequest) { Map prop = dispatchRequest.getPropertiesMap(); - if (prop == null) { + if (prop == null || dispatchRequest.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { return; } String multiDispatchQueue = prop.get(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); @@ -2135,24 +2878,412 @@ private void notifyMessageArrive4MultiQueue(DispatchRequest dispatchRequest) { @Override public void run() { - DefaultMessageStore.log.info(this.getServiceName() + " service started"); + DefaultMessageStore.LOGGER.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { - Thread.sleep(1); + TimeUnit.MILLISECONDS.sleep(1); this.doReput(); } catch (Exception e) { - DefaultMessageStore.log.warn(this.getServiceName() + " service has exception. ", e); + DefaultMessageStore.LOGGER.warn(this.getServiceName() + " service has exception. ", e); } } - DefaultMessageStore.log.info(this.getServiceName() + " service end"); + 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 = new ThreadPoolExecutor( + 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() { + dispatchRequestsList.clear(); + dispatchRequestOrderlyQueue.get(dispatchRequestsList); + if (!dispatchRequestsList.isEmpty()) { + for (DispatchRequest[] dispatchRequests : dispatchRequestsList) { + for (DispatchRequest dispatchRequest : dispatchRequests) { + DefaultMessageStore.this.doDispatch(dispatchRequest); + // wake up long-polling + 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); + } + 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 < DefaultMessageStore.this.getConfirmOffset() && 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 DefaultStoreMetricsManager.getMetricsView(); + } + + @Override + public void initMetrics(Meter meter, Supplier attributesBuilderSupplier) { + 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); + } + + public long getReputFromOffset() { + return this.reputMessageService.getReputFromOffset(); + } } diff --git a/store/src/main/java/org/apache/rocketmq/store/DispatchRequest.java b/store/src/main/java/org/apache/rocketmq/store/DispatchRequest.java index 89d47ced5ba..79d006bafc3 100644 --- a/store/src/main/java/org/apache/rocketmq/store/DispatchRequest.java +++ b/store/src/main/java/org/apache/rocketmq/store/DispatchRequest.java @@ -37,6 +37,14 @@ public class DispatchRequest { 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, @@ -58,6 +66,7 @@ public DispatchRequest( this.tagsCode = tagsCode; this.storeTimestamp = storeTimestamp; this.consumeQueueOffset = consumeQueueOffset; + this.msgBaseOffset = consumeQueueOffset; this.keys = keys; this.uniqKey = uniqKey; @@ -67,6 +76,22 @@ public DispatchRequest( 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; @@ -159,10 +184,26 @@ 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; } @@ -170,4 +211,34 @@ public int getBufferSize() { 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; + } + + @Override + public String toString() { + return "DispatchRequest{" + + "topic='" + topic + '\'' + + ", queueId=" + queueId + + ", commitLogOffset=" + commitLogOffset + + ", msgSize=" + msgSize + + ", success=" + success + + ", msgBaseOffset=" + msgBaseOffset + + ", batchSize=" + batchSize + + ", nextReputFromOffset=" + nextReputFromOffset + + '}'; + } } diff --git a/store/src/main/java/org/apache/rocketmq/store/FileQueueSnapshot.java b/store/src/main/java/org/apache/rocketmq/store/FileQueueSnapshot.java new file mode 100644 index 00000000000..6521a76b70d --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/FileQueueSnapshot.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + + '}'; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/FlushDiskWatcher.java b/store/src/main/java/org/apache/rocketmq/store/FlushDiskWatcher.java index 980a496c55a..a75efd6258e 100644 --- a/store/src/main/java/org/apache/rocketmq/store/FlushDiskWatcher.java +++ b/store/src/main/java/org/apache/rocketmq/store/FlushDiskWatcher.java @@ -21,12 +21,12 @@ import org.apache.rocketmq.common.ServiceThread; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final LinkedBlockingQueue commitRequests = new LinkedBlockingQueue<>(); @Override diff --git a/store/src/main/java/org/apache/rocketmq/store/FlushManager.java b/store/src/main/java/org/apache/rocketmq/store/FlushManager.java new file mode 100644 index 00000000000..fe3951ae7f7 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/FlushManager.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java b/store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java index 4e6eccbfbf0..a7556dfb855 100644 --- a/store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java +++ b/store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java @@ -18,13 +18,14 @@ import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import org.apache.rocketmq.store.stats.BrokerStatsManager; public class GetMessageResult { private final List messageMapedList; private final List messageBufferList; + private final List messageQueueOffset; private GetMessageStatus status; private long nextBeginOffset; @@ -33,18 +34,40 @@ public class GetMessageResult { 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; + + 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() { @@ -92,7 +115,24 @@ public void addMessage(final SelectMappedBufferResult mapedBuffer) { this.messageBufferList.add(mapedBuffer.getByteBuffer()); this.bufferTotalSize += mapedBuffer.getSize(); this.msgCount4Commercial += (int) Math.ceil( - mapedBuffer.getSize() / BrokerStatsManager.SIZE_PER_COUNT); + 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() { @@ -105,12 +145,8 @@ public int getBufferTotalSize() { return bufferTotalSize; } - public void setBufferTotalSize(int bufferTotalSize) { - this.bufferTotalSize = bufferTotalSize; - } - public int getMessageCount() { - return this.messageMapedList.size(); + return messageCount; } public boolean isSuggestPullingFromSlave() { @@ -129,11 +165,22 @@ 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; + } + @Override public String toString() { return "GetMessageResult [status=" + status + ", nextBeginOffset=" + nextBeginOffset + ", minOffset=" - + minOffset + ", maxOffset=" + maxOffset + ", bufferTotalSize=" + bufferTotalSize + + minOffset + ", maxOffset=" + maxOffset + ", bufferTotalSize=" + bufferTotalSize + ", messageCount=" + messageCount + ", suggestPullingFromSlave=" + suggestPullingFromSlave + "]"; } - } diff --git a/store/src/main/java/org/apache/rocketmq/store/GetMessageStatus.java b/store/src/main/java/org/apache/rocketmq/store/GetMessageStatus.java index 6a824b8981b..bc244865ffe 100644 --- a/store/src/main/java/org/apache/rocketmq/store/GetMessageStatus.java +++ b/store/src/main/java/org/apache/rocketmq/store/GetMessageStatus.java @@ -35,4 +35,6 @@ public enum GetMessageStatus { NO_MATCHED_LOGIC_QUEUE, NO_MESSAGE_IN_QUEUE, + + OFFSET_RESET } diff --git a/store/src/main/java/org/apache/rocketmq/store/MappedFile.java b/store/src/main/java/org/apache/rocketmq/store/MappedFile.java deleted file mode 100644 index 774896bb90c..00000000000 --- a/store/src/main/java/org/apache/rocketmq/store/MappedFile.java +++ /dev/null @@ -1,587 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.Method; -import java.nio.ByteBuffer; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.FileChannel.MapMode; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import org.apache.rocketmq.common.UtilAll; -import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.message.MessageExt; -import org.apache.rocketmq.common.message.MessageExtBatch; -import org.apache.rocketmq.store.CommitLog.PutMessageContext; -import org.apache.rocketmq.store.config.FlushDiskType; -import org.apache.rocketmq.store.util.LibC; -import sun.nio.ch.DirectBuffer; - -public class MappedFile extends ReferenceResource { - public static final int OS_PAGE_SIZE = 1024 * 4; - protected static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); - - private static final AtomicLong TOTAL_MAPPED_VIRTUAL_MEMORY = new AtomicLong(0); - - private static final AtomicInteger TOTAL_MAPPED_FILES = new AtomicInteger(0); - protected final AtomicInteger wrotePosition = new AtomicInteger(0); - protected final AtomicInteger committedPosition = new AtomicInteger(0); - private final AtomicInteger flushedPosition = new AtomicInteger(0); - 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; - private String fileName; - private long fileFromOffset; - private File file; - private MappedByteBuffer mappedByteBuffer; - private volatile long storeTimestamp = 0; - private boolean firstCreateInQueue = false; - - public MappedFile() { - } - - public MappedFile(final String fileName, final int fileSize) throws IOException { - init(fileName, fileSize); - } - - public MappedFile(final String fileName, final int fileSize, - final TransientStorePool transientStorePool) throws IOException { - init(fileName, fileSize, transientStorePool); - } - - public static void ensureDirOK(final String dirName) { - if (dirName != null) { - File f = new File(dirName); - if (!f.exists()) { - boolean result = f.mkdirs(); - log.info(dirName + " mkdir " + (result ? "OK" : "Failed")); - } - } - } - - public static void clean(final ByteBuffer buffer) { - if (buffer == null || !buffer.isDirect() || buffer.capacity() == 0) - return; - invoke(invoke(viewed(buffer), "cleaner"), "clean"); - } - - private static Object invoke(final Object target, final String methodName, final Class... args) { - return AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { - try { - Method method = method(target, methodName, args); - method.setAccessible(true); - return method.invoke(target); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - }); - } - - private static Method method(Object target, String methodName, Class[] args) - throws NoSuchMethodException { - try { - return target.getClass().getMethod(methodName, args); - } catch (NoSuchMethodException e) { - return target.getClass().getDeclaredMethod(methodName, args); - } - } - - private static ByteBuffer viewed(ByteBuffer buffer) { - String methodName = "viewedBuffer"; - Method[] methods = buffer.getClass().getMethods(); - for (int i = 0; i < methods.length; i++) { - if (methods[i].getName().equals("attachment")) { - methodName = "attachment"; - break; - } - } - - ByteBuffer viewedBuffer = (ByteBuffer) invoke(buffer, methodName); - if (viewedBuffer == null) - return buffer; - else - return viewed(viewedBuffer); - } - - public static int getTotalMappedFiles() { - return TOTAL_MAPPED_FILES.get(); - } - - public static long getTotalMappedVirtualMemory() { - return TOTAL_MAPPED_VIRTUAL_MEMORY.get(); - } - - public void init(final String fileName, final int fileSize, - final TransientStorePool transientStorePool) throws IOException { - init(fileName, fileSize); - this.writeBuffer = transientStorePool.borrowBuffer(); - this.transientStorePool = transientStorePool; - } - - private void init(final String fileName, final int fileSize) throws IOException { - this.fileName = fileName; - this.fileSize = fileSize; - this.file = new File(fileName); - this.fileFromOffset = Long.parseLong(this.file.getName()); - boolean ok = false; - - ensureDirOK(this.file.getParent()); - - try { - this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel(); - 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(); - } - } - } - - public long getLastModifiedTimestamp() { - return this.file.lastModified(); - } - - public int getFileSize() { - return fileSize; - } - - public FileChannel getFileChannel() { - return fileChannel; - } - - public AppendMessageResult appendMessage(final MessageExtBrokerInner msg, final AppendMessageCallback cb, - PutMessageContext putMessageContext) { - return appendMessagesInner(msg, cb, putMessageContext); - } - - 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 = this.wrotePosition.get(); - - if (currentPos < this.fileSize) { - ByteBuffer byteBuffer = writeBuffer != null ? writeBuffer.slice() : this.mappedByteBuffer.slice(); - byteBuffer.position(currentPos); - AppendMessageResult result; - if (messageExt instanceof MessageExtBrokerInner) { - result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, - (MessageExtBrokerInner) messageExt, putMessageContext); - } else if (messageExt instanceof MessageExtBatch) { - result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, - (MessageExtBatch) messageExt, putMessageContext); - } else { - return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); - } - this.wrotePosition.addAndGet(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); - } - - public long getFileFromOffset() { - return this.fileFromOffset; - } - - public boolean appendMessage(final byte[] data) { - int currentPos = this.wrotePosition.get(); - - if ((currentPos + data.length) <= this.fileSize) { - try { - ByteBuffer buf = this.mappedByteBuffer.slice(); - buf.position(currentPos); - buf.put(data); - } catch (Throwable e) { - log.error("Error occurred when append message to mappedFile.", e); - } - this.wrotePosition.addAndGet(data.length); - return true; - } - - return false; - } - - /** - * Content of data from offset to offset + length will be wrote to file. - * - * @param offset The offset of the subarray to be used. - * @param length The length of the subarray to be used. - */ - public boolean appendMessage(final byte[] data, final int offset, final int length) { - int currentPos = this.wrotePosition.get(); - - if ((currentPos + length) <= this.fileSize) { - try { - ByteBuffer buf = this.mappedByteBuffer.slice(); - buf.position(currentPos); - buf.put(data, offset, length); - } catch (Throwable e) { - log.error("Error occurred when append message to mappedFile.", e); - } - this.wrotePosition.addAndGet(length); - return true; - } - - return false; - } - - /** - * @return The current flushed position - */ - public int flush(final int flushLeastPages) { - if (this.isAbleToFlush(flushLeastPages)) { - if (this.hold()) { - int value = getReadPosition(); - - try { - //We only append data to fileChannel or mappedByteBuffer, never both. - if (writeBuffer != null || this.fileChannel.position() != 0) { - this.fileChannel.force(false); - } else { - this.mappedByteBuffer.force(); - } - } catch (Throwable e) { - log.error("Error occurred when force data to disk.", e); - } - - this.flushedPosition.set(value); - this.release(); - } else { - log.warn("in flush, hold failed, flush offset = " + this.flushedPosition.get()); - this.flushedPosition.set(getReadPosition()); - } - } - return this.getFlushedPosition(); - } - - public int commit(final int commitLeastPages) { - if (writeBuffer == null) { - //no need to commit data to file channel, so just regard wrotePosition as committedPosition. - return this.wrotePosition.get(); - } - if (this.isAbleToCommit(commitLeastPages)) { - if (this.hold()) { - commit0(); - this.release(); - } else { - log.warn("in commit, hold failed, commit offset = " + this.committedPosition.get()); - } - } - - // All dirty data has been committed to FileChannel. - if (writeBuffer != null && this.transientStorePool != null && this.fileSize == this.committedPosition.get()) { - this.transientStorePool.returnBuffer(writeBuffer); - this.writeBuffer = null; - } - - return this.committedPosition.get(); - } - - protected void commit0() { - int writePos = this.wrotePosition.get(); - int lastCommittedPosition = this.committedPosition.get(); - - if (writePos - lastCommittedPosition > 0) { - try { - ByteBuffer byteBuffer = writeBuffer.slice(); - byteBuffer.position(lastCommittedPosition); - byteBuffer.limit(writePos); - this.fileChannel.position(lastCommittedPosition); - this.fileChannel.write(byteBuffer); - this.committedPosition.set(writePos); - } catch (Throwable e) { - log.error("Error occurred when commit data to FileChannel.", e); - } - } - } - - private boolean isAbleToFlush(final int flushLeastPages) { - int flush = this.flushedPosition.get(); - 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 flush = this.committedPosition.get(); - int write = this.wrotePosition.get(); - - if (this.isFull()) { - return true; - } - - if (commitLeastPages > 0) { - return ((write / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE)) >= commitLeastPages; - } - - return write > flush; - } - - public int getFlushedPosition() { - return flushedPosition.get(); - } - - public void setFlushedPosition(int pos) { - this.flushedPosition.set(pos); - } - - public boolean isFull() { - return this.fileSize == this.wrotePosition.get(); - } - - public SelectMappedBufferResult selectMappedBuffer(int pos, int size) { - int readPosition = getReadPosition(); - if ((pos + size) <= readPosition) { - if (this.hold()) { - 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; - } - - public SelectMappedBufferResult selectMappedBuffer(int pos) { - int readPosition = getReadPosition(); - if (pos < readPosition && pos >= 0) { - if (this.hold()) { - 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; - } - - clean(this.mappedByteBuffer); - TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(this.fileSize * (-1)); - TOTAL_MAPPED_FILES.decrementAndGet(); - log.info("unmap file[REF:" + currentRef + "] " + this.fileName + " OK"); - return true; - } - - public boolean destroy(final long intervalForcibly) { - this.shutdown(intervalForcibly); - - if (this.isCleanupOver()) { - try { - 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)); - } 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; - } - - public int getWrotePosition() { - return wrotePosition.get(); - } - - public void setWrotePosition(int pos) { - this.wrotePosition.set(pos); - } - - /** - * @return The max position which have valid data - */ - public int getReadPosition() { - return this.writeBuffer == null ? this.wrotePosition.get() : this.committedPosition.get(); - } - - public void setCommittedPosition(int pos) { - this.committedPosition.set(pos); - } - - public void warmMappedFile(FlushDiskType type, int pages) { - long beginTime = System.currentTimeMillis(); - ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); - int flush = 0; - long time = System.currentTimeMillis(); - for (int i = 0, j = 0; i < this.fileSize; i += MappedFile.OS_PAGE_SIZE, j++) { - byteBuffer.put(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(); - } - - public String getFileName() { - return fileName; - } - - public MappedByteBuffer getMappedByteBuffer() { - return mappedByteBuffer; - } - - public ByteBuffer sliceByteBuffer() { - return this.mappedByteBuffer.slice(); - } - - public long getStoreTimestamp() { - return storeTimestamp; - } - - public boolean isFirstCreateInQueue() { - return firstCreateInQueue; - } - - public void setFirstCreateInQueue(boolean firstCreateInQueue) { - this.firstCreateInQueue = firstCreateInQueue; - } - - public void mlock() { - final long beginTime = System.currentTimeMillis(); - final long address = ((DirectBuffer) (this.mappedByteBuffer)).address(); - 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); - } - } - - public void munlock() { - final long beginTime = System.currentTimeMillis(); - final long address = ((DirectBuffer) (this.mappedByteBuffer)).address(); - 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); - } - - //testable - File getFile() { - return this.file; - } - - @Override - public String toString() { - return this.fileName; - } -} diff --git a/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java b/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java index ddfe65bb9f1..0bc70642fe9 100644 --- a/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java +++ b/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java @@ -16,8 +16,10 @@ */ 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; @@ -25,29 +27,32 @@ 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.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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 { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); - private static final InternalLogger LOG_ERROR = InternalLoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME); +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); - private static final int DELETE_FILES_BATCH_MAX = 10; - - private final String storePath; + protected final String storePath; protected final int mappedFileSize; - protected final CopyOnWriteArrayList mappedFiles = new CopyOnWriteArrayList(); + protected final CopyOnWriteArrayList mappedFiles = new CopyOnWriteArrayList<>(); - private final AllocateMappedFileService allocateMappedFileService; + protected final AllocateMappedFileService allocateMappedFileService; protected long flushedWhere = 0; - private long committedWhere = 0; + protected long committedWhere = 0; - private volatile long storeTimestamp = 0; + protected volatile long storeTimestamp = 0; public MappedFileQueue(final String storePath, int mappedFileSize, AllocateMappedFileService allocateMappedFileService) { @@ -57,8 +62,8 @@ public MappedFileQueue(final String storePath, int mappedFileSize, } public void checkSelf() { - - if (!this.mappedFiles.isEmpty()) { + List mappedFiles = new ArrayList<>(this.mappedFiles); + if (!mappedFiles.isEmpty()) { Iterator iterator = mappedFiles.iterator(); MappedFile pre = null; while (iterator.hasNext()) { @@ -75,6 +80,89 @@ public void checkSelf() { } } + 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); @@ -91,7 +179,7 @@ public MappedFile getMappedFileByTime(final long timestamp) { return (MappedFile) mfs[mfs.length - 1]; } - private Object[] copyMappedFiles(final int reservedMappedFiles) { + protected Object[] copyMappedFiles(final int reservedMappedFiles) { Object[] mfs; if (this.mappedFiles.size() <= reservedMappedFiles) { @@ -103,7 +191,7 @@ private Object[] copyMappedFiles(final int reservedMappedFiles) { } public void truncateDirtyFiles(long offset) { - List willRemoveFiles = new ArrayList(); + List willRemoveFiles = new ArrayList<>(); for (MappedFile file : this.mappedFiles) { long fileTailOffset = file.getFileFromOffset() + this.mappedFileSize; @@ -159,7 +247,18 @@ public boolean doLoad(List files) { // ascending order files.sort(Comparator.comparing(File::getName)); - for (File file : files) { + 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"); @@ -167,7 +266,7 @@ public boolean doLoad(List files) { } try { - MappedFile mappedFile = new MappedFile(file.getPath(), mappedFileSize); + MappedFile mappedFile = new DefaultMappedFile(file.getPath(), mappedFileSize); mappedFile.setWrotePosition(this.mappedFileSize); mappedFile.setFlushedPosition(this.mappedFileSize); @@ -216,7 +315,33 @@ public MappedFile getLastMappedFile(final long startOffset, boolean needCreate) return mappedFileLast; } - protected MappedFile tryCreateMappedFile(long createOffset) { + 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); @@ -231,7 +356,7 @@ protected MappedFile doCreateMappedFile(String nextFilePath, String nextNextFile nextNextFilePath, this.mappedFileSize); } else { try { - mappedFile = new MappedFile(nextFilePath, this.mappedFileSize); + mappedFile = new DefaultMappedFile(nextFilePath, this.mappedFileSize); } catch (IOException e) { log.error("create mappedFile exception", e); } @@ -252,21 +377,8 @@ public MappedFile getLastMappedFile(final long startOffset) { } 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; + MappedFile[] mappedFiles = this.mappedFiles.toArray(new MappedFile[0]); + return mappedFiles.length == 0 ? null : mappedFiles[mappedFiles.length - 1]; } public boolean resetOffset(long offset) { @@ -282,7 +394,7 @@ public boolean resetOffset(long offset) { return false; } - ListIterator iterator = this.mappedFiles.listIterator(); + ListIterator iterator = this.mappedFiles.listIterator(mappedFiles.size()); while (iterator.hasPrevious()) { mappedFileLast = iterator.previous(); @@ -350,7 +462,8 @@ public void deleteLastMappedFile() { public int deleteExpiredFileByTime(final long expiredTime, final int deleteFilesInterval, final long intervalForcibly, - final boolean cleanImmediately) { + final boolean cleanImmediately, + final int deleteFileBatchMax) { Object[] mfs = this.copyMappedFiles(0); if (null == mfs) @@ -358,17 +471,23 @@ public int deleteExpiredFileByTime(final long expiredTime, int mfsLength = mfs.length - 1; int deleteCount = 0; - List files = new ArrayList(); + 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() >= DELETE_FILES_BATCH_MAX) { + if (files.size() >= deleteFileBatchMax) { break; } @@ -382,6 +501,7 @@ public int deleteExpiredFileByTime(final long expiredTime, break; } } else { + skipFileNum++; //avoid deleting files in the middle break; } @@ -396,7 +516,7 @@ public int deleteExpiredFileByTime(final long expiredTime, public int deleteExpiredFileByOffset(long offset, int unitSize) { Object[] mfs = this.copyMappedFiles(0); - List files = new ArrayList(); + List files = new ArrayList<>(); int deleteCount = 0; if (null != mfs) { @@ -436,6 +556,64 @@ public int deleteExpiredFileByOffset(long offset, int unitSize) { 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.flushedWhere, this.flushedWhere == 0); @@ -453,7 +631,7 @@ public boolean flush(final int flushLeastPages) { return result; } - public boolean commit(final int commitLeastPages) { + public synchronized boolean commit(final int commitLeastPages) { boolean result = true; MappedFile mappedFile = this.findMappedFileByOffset(this.committedWhere, this.committedWhere == 0); if (mappedFile != null) { @@ -560,7 +738,7 @@ public boolean retryDeleteFirstFile(final long intervalForcibly) { boolean result = mappedFile.destroy(intervalForcibly); if (result) { log.info("the mappedFile re delete OK, " + mappedFile.getFileName()); - List tmpFiles = new ArrayList(); + List tmpFiles = new ArrayList<>(); tmpFiles.add(mappedFile); this.deleteExpiredFile(tmpFiles); } else { @@ -594,6 +772,70 @@ public void destroy() { } } + @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; } @@ -621,4 +863,34 @@ public long getCommittedWhere() { 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; + } } diff --git a/store/src/main/java/org/apache/rocketmq/store/MessageExtEncoder.java b/store/src/main/java/org/apache/rocketmq/store/MessageExtEncoder.java new file mode 100644 index 00000000000..ee609a337bc --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/MessageExtEncoder.java @@ -0,0 +1,321 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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 java.nio.ByteBuffer; + +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; + public MessageExtEncoder(final int maxMessageBodySize) { + ByteBufAllocator alloc = UnpooledByteBufAllocator.DEFAULT; + //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.maxMessageBodySize = maxMessageBodySize; + this.maxMessageSize = maxMessageSize; + } + + 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 PutMessageResult encode(MessageExtBrokerInner msgInner) { + this.byteBuf.clear(); + /** + * 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 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 > 0) + this.byteBuf.writeBytes(propertiesData); + + 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"); + } + + // properties from MessageExtBatch + String batchPropStr = MessageDecoder.messageProperties2String(messageExtBatch.getProperties()); + final byte[] batchPropData = batchPropStr.getBytes(MessageDecoder.CHARSET_UTF8); + int batchPropDataLen = batchPropData.length; + if (batchPropDataLen > Short.MAX_VALUE) { + CommitLog.log.warn("Properties size of messageExtBatch exceeded, properties size: {}, maxSize: {}.", batchPropDataLen, Short.MAX_VALUE); + throw new RuntimeException("Properties size of messageExtBatch exceeded!"); + } + final short batchPropLen = (short) batchPropDataLen; + + 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); + boolean needAppendLastPropertySeparator = propertiesLen > 0 && batchPropLen > 0 + && messagesByteBuff.get(messagesByteBuff.position() - 1) != MessageDecoder.PROPERTY_SEPARATOR; + + final byte[] topicData = messageExtBatch.getTopic().getBytes(MessageDecoder.CHARSET_UTF8); + + final int topicLength = topicData.length; + final int topicLengthSize = messageExtBatch.getVersion().getTopicLengthSize(); + int totalPropLen = needAppendLastPropertySeparator ? + propertiesLen + batchPropLen + topicLengthSize : propertiesLen + batchPropLen; + + 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); + } + if (batchPropLen > 0) { + if (needAppendLastPropertySeparator) { + this.byteBuf.writeByte((byte) MessageDecoder.PROPERTY_SEPARATOR); + } + this.byteBuf.writeBytes(batchPropData, 0, batchPropLen); + } + } + putMessageContext.setBatchSize(batchSize); + putMessageContext.setPhyPos(new long[batchSize]); + + return this.byteBuf.nioBuffer(); + } + + public ByteBuffer getEncoderBuffer() { + return this.byteBuf.nioBuffer(); + } + + 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(int size) { + encoder = new MessageExtEncoder(size); + keyBuilder = new StringBuilder(); + } + + public MessageExtEncoder getEncoder() { + return encoder; + } + + public StringBuilder getKeyBuilder() { + return keyBuilder; + } + } + +} diff --git a/store/src/main/java/org/apache/rocketmq/store/MessageStore.java b/store/src/main/java/org/apache/rocketmq/store/MessageStore.java index 6771ede633a..3db0c18f7f8 100644 --- a/store/src/main/java/org/apache/rocketmq/store/MessageStore.java +++ b/store/src/main/java/org/apache/rocketmq/store/MessageStore.java @@ -16,15 +16,35 @@ */ 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.View; + +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.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.store.config.BrokerRole; +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.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.ConsumeQueueStore; import org.apache.rocketmq.store.stats.BrokerStatsManager; +import org.apache.rocketmq.store.timer.TimerMessageStore; +import org.apache.rocketmq.store.util.PerfCounter; /** * This class defines contracting interfaces to implement, allowing third-party vendor to use customized message store. @@ -55,9 +75,9 @@ public interface MessageStore { */ 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 + /** + * 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 @@ -68,6 +88,7 @@ default CompletableFuture asyncPutMessage(final MessageExtBrok /** * Store a batch of messages in async manner + * * @param messageExtBatch the message batch * @return a CompletableFuture for the result of store operation */ @@ -95,40 +116,101 @@ default CompletableFuture asyncPutMessages(final MessageExtBat * 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 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 topic Topic name. * @param queueId Queue ID. * @return Maximum offset at present. */ long getMaxOffsetInQueue(final String topic, final int queueId); + /** + * 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); + /** * Get the minimum offset of the topic queue. * - * @param topic Topic name. + * @param topic Topic name. * @param queueId Queue ID. * @return Minimum offset at present. */ long getMinOffsetInQueue(final String topic, final int queueId); + TimerMessageStore getTimerMessageStore(); + + void setTimerMessageStore(TimerMessageStore timerMessageStore); + /** * 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 topic Topic of the message to lookup. + * @param queueId Queue ID. * @param consumeQueueOffset offset of consume queue. * @return physical offset. */ @@ -137,8 +219,8 @@ GetMessageResult getMessage(final String group, final String topic, final int qu /** * 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 topic Topic of the message. + * @param queueId Queue ID. * @param timestamp Timestamp to look up. * @return physical offset which matches. */ @@ -152,6 +234,15 @@ GetMessageResult getMessage(final String group, final String topic, final int qu */ 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. * @@ -164,7 +255,7 @@ GetMessageResult getMessage(final String group, final String topic, final int qu * Get one message from the specified commit log offset. * * @param commitLogOffset commit log offset. - * @param msgSize message size. + * @param msgSize message size. * @return wrapped result of the message. */ SelectMappedBufferResult selectOneMessageByOffset(final long commitLogOffset, final int msgSize); @@ -176,6 +267,8 @@ GetMessageResult getMessage(final String group, final String topic, final int qu */ String getRunningDataInfo(); + long getTimingMessageCount(String topic); + /** * Message store runtime information, which should generally contains various statistical information. * @@ -183,6 +276,12 @@ GetMessageResult getMessage(final String group, final String topic, final int qu */ HashMap getRuntimeInfo(); + /** + * HA runtime information + * @return runtime information of ha + */ + HARuntimeInfo getHARuntimeInfo(); + /** * Get the maximum commit log offset. * @@ -200,7 +299,7 @@ GetMessageResult getMessage(final String group, final String topic, final int qu /** * Get the store time of the earliest message in the given queue. * - * @param topic Topic of the messages to query. + * @param topic Topic of the messages to query. * @param queueId Queue ID to find. * @return store time of the earliest message. */ @@ -213,20 +312,40 @@ GetMessageResult getMessage(final String group, final String topic, final int qu */ 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 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 topic Topic * @param queueId Queue ID. * @return total number. */ @@ -240,13 +359,22 @@ GetMessageResult getMessage(final String group, final String topic, final int qu */ 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 + * @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); @@ -259,15 +387,28 @@ GetMessageResult getMessage(final String group, final String topic, final int qu /** * Query messages by given key. * - * @param topic topic of the message. - * @param key message 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. + * @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); + /** + * 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); + /** * Update HA master address. * @@ -275,6 +416,13 @@ QueryMessageResult queryMessage(final String topic, final String key, final int */ void updateHaMasterAddress(final String newAddr); + /** + * Update master address. + * + * @param newAddr new address. + */ + void updateMasterAddress(final String newAddr); + /** * Return how much the slave falls behind. * @@ -290,12 +438,21 @@ QueryMessageResult queryMessage(final String topic, final String key, final int long now(); /** - * Clean unused topics. + * Delete topic's consume queue file and unused stats. + * This interface allows user delete system topic. * - * @param topics all valid topics. + * @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 topics); + int cleanUnusedTopic(final Set retainTopics); /** * Clean expired consume queues. @@ -305,13 +462,35 @@ QueryMessageResult queryMessage(final String topic, final String key, final int /** * Check if the given message has been swapped out of the memory. * - * @param topic topic. - * @param queueId queue ID. + * @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. * @@ -326,6 +505,13 @@ QueryMessageResult queryMessage(final String topic, final String key, final int */ long flush(); + /** + * Get the current flushed offset. + * + * @return flushed offset + */ + long getFlushedWhere(); + /** * Reset written offset. * @@ -377,13 +563,28 @@ QueryMessageResult queryMessage(final String topic, final String key, final int LinkedList getDispatcherList(); /** - * Get consume queue of the topic/queue. + * 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 topic Topic. * @param queueId Queue ID. * @return Consume queue. */ - ConsumeQueue getConsumeQueue(String topic, int queueId); + 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. @@ -393,18 +594,371 @@ QueryMessageResult queryMessage(final String topic, final String key, final int BrokerStatsManager getBrokerStatsManager(); /** - * handle - * @param brokerRole + * 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' + */ + void onCommitLogDispatch(DispatchRequest dispatchRequest, boolean doDispatch, MappedFile commitLogFile, + boolean isRecover, boolean isFileEnd); + + /** + * 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 + */ + void truncateDirtyLogicFiles(long phyOffset); + + /** + * Destroy logics files + */ + void destroyLogics(); + + /** + * 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 + */ + ConsumeQueueStore 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 + */ + void assignOffset(MessageExtBrokerInner msg); + + /** + * 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 handleScheduleMessageService(BrokerRole brokerRole); + void setMasterFlushedOffset(long masterFlushedOffset); /** - * Clean unused lmq topic. - * When calling to clean up the lmq topic, - * the lmq topic cannot be used to write messages at the same time, - * otherwise the messages of the cleaning lmq topic may be lost, - * please call this method with caution - * @param topic lmq topic + * 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 + */ + boolean truncateFiles(long offsetToTruncate); + + /** + * Check if the offset is align with one message. + * + * @param offset offset to check + * @return true if align, 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(); + + /** + * 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 cleanUnusedLmqTopic(String topic); + void initMetrics(Meter meter, Supplier attributesBuilderSupplier); } diff --git a/store/src/main/java/org/apache/rocketmq/store/MultiDispatch.java b/store/src/main/java/org/apache/rocketmq/store/MultiDispatch.java deleted file mode 100644 index 679eed12344..00000000000 --- a/store/src/main/java/org/apache/rocketmq/store/MultiDispatch.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.store.CommitLog.MessageExtEncoder; - -/** - * not-thread-safe - */ -public class MultiDispatch { - private static final InternalLogger LOGGER = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); - private final StringBuilder keyBuilder = new StringBuilder(); - private final DefaultMessageStore messageStore; - private final CommitLog commitLog; - - public MultiDispatch(DefaultMessageStore messageStore, CommitLog commitLog) { - this.messageStore = messageStore; - this.commitLog = commitLog; - } - - public String queueKey(String queueName, MessageExtBrokerInner msgInner) { - keyBuilder.setLength(0); - keyBuilder.append(queueName); - keyBuilder.append('-'); - int queueId = msgInner.getQueueId(); - if (messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queueName)) { - queueId = 0; - } - keyBuilder.append(queueId); - return keyBuilder.toString(); - } - - public boolean wrapMultiDispatch(final MessageExtBrokerInner msgInner) { - if (!messageStore.getMessageStoreConfig().isEnableMultiDispatch()) { - return true; - } - String multiDispatchQueue = msgInner.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); - if (StringUtils.isBlank(multiDispatchQueue)) { - return true; - } - String[] queues = multiDispatchQueue.split(MixAll.MULTI_DISPATCH_QUEUE_SPLITTER); - Long[] queueOffsets = new Long[queues.length]; - for (int i = 0; i < queues.length; i++) { - String key = queueKey(queues[i], msgInner); - Long queueOffset; - try { - queueOffset = getTopicQueueOffset(key); - } catch (Exception e) { - return false; - } - if (null == queueOffset) { - queueOffset = 0L; - if (messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(key)) { - commitLog.getLmqTopicQueueTable().put(key, queueOffset); - } else { - commitLog.getTopicQueueTable().put(key, queueOffset); - } - } - queueOffsets[i] = queueOffset; - } - MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET, - StringUtils.join(queueOffsets, MixAll.MULTI_DISPATCH_QUEUE_SPLITTER)); - removeWaitStorePropertyString(msgInner); - return rebuildMsgInner(msgInner); - } - - private void removeWaitStorePropertyString(MessageExtBrokerInner msgInner) { - if (msgInner.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 = msgInner.getProperties().remove(MessageConst.PROPERTY_WAIT_STORE_MSG_OK); - msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); - // Reput to properties, since msgInner.isWaitStoreMsgOK() will be invoked later - msgInner.getProperties().put(MessageConst.PROPERTY_WAIT_STORE_MSG_OK, waitStoreMsgOKValue); - } else { - msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); - } - } - - private boolean rebuildMsgInner(MessageExtBrokerInner msgInner) { - MessageExtEncoder encoder = this.commitLog.getPutMessageThreadLocal().get().getEncoder(); - PutMessageResult encodeResult = encoder.encode(msgInner); - if (encodeResult != null) { - LOGGER.error("rebuild msgInner for multiDispatch", encodeResult); - return false; - } - msgInner.setEncodedBuff(encoder.getEncoderBuffer()); - return true; - - } - - public void updateMultiQueueOffset(final MessageExtBrokerInner msgInner) { - if (!messageStore.getMessageStoreConfig().isEnableMultiDispatch()) { - return; - } - String multiDispatchQueue = msgInner.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH); - if (StringUtils.isBlank(multiDispatchQueue)) { - return; - } - String multiQueueOffset = msgInner.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET); - if (StringUtils.isBlank(multiQueueOffset)) { - LOGGER.error("[bug] no multiQueueOffset when updating {}", msgInner.getTopic()); - return; - } - String[] queues = multiDispatchQueue.split(MixAll.MULTI_DISPATCH_QUEUE_SPLITTER); - String[] queueOffsets = multiQueueOffset.split(MixAll.MULTI_DISPATCH_QUEUE_SPLITTER); - if (queues.length != queueOffsets.length) { - LOGGER.error("[bug] num is not equal when updateMultiQueueOffset {}", msgInner.getTopic()); - return; - } - for (int i = 0; i < queues.length; i++) { - String key = queueKey(queues[i], msgInner); - long queueOffset = Long.parseLong(queueOffsets[i]); - if (messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(key)) { - commitLog.getLmqTopicQueueTable().put(key, ++queueOffset); - } else { - commitLog.getTopicQueueTable().put(key, ++queueOffset); - } - } - } - - private Long getTopicQueueOffset(String key) throws Exception { - Long offset = null; - if (messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(key)) { - Long queueNextOffset = commitLog.getLmqTopicQueueTable().get(key); - if (queueNextOffset != null) { - offset = queueNextOffset; - } - } else { - offset = commitLog.getTopicQueueTable().get(key); - } - return offset; - } - -} diff --git a/store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java b/store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java index 669698ff4e9..8f5af94380c 100644 --- a/store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java +++ b/store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java @@ -22,8 +22,10 @@ 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; @@ -44,7 +46,7 @@ public MultiPathMappedFileQueue(MessageStoreConfig messageStoreConfig, int mappe } private Set getPaths() { - String[] paths = config.getStorePathCommitLog().trim().split(MessageStoreConfig.MULTI_PATH_SPLITTER); + String[] paths = config.getStorePathCommitLog().trim().split(MixAll.MULTI_PATH_SPLITTER); return new HashSet<>(Arrays.asList(paths)); } @@ -53,7 +55,7 @@ private Set getReadonlyPaths() { if (StringUtils.isBlank(pathStr)) { return Collections.emptySet(); } - String[] paths = pathStr.trim().split(MessageStoreConfig.MULTI_PATH_SPLITTER); + String[] paths = pathStr.trim().split(MixAll.MULTI_PATH_SPLITTER); return new HashSet<>(Arrays.asList(paths)); } @@ -75,7 +77,7 @@ public boolean load() { } @Override - protected MappedFile tryCreateMappedFile(long createOffset) { + public MappedFile tryCreateMappedFile(long createOffset) { long fileIdx = createOffset / this.mappedFileSize; Set storePath = getPaths(); Set readonlyPathSet = getReadonlyPaths(); diff --git a/store/src/main/java/org/apache/rocketmq/store/PutMessageContext.java b/store/src/main/java/org/apache/rocketmq/store/PutMessageContext.java new file mode 100644 index 00000000000..bf8832d9a22 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/PutMessageContext.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} \ No newline at end of file diff --git a/store/src/main/java/org/apache/rocketmq/store/PutMessageResult.java b/store/src/main/java/org/apache/rocketmq/store/PutMessageResult.java index e12cc0ca484..bcca6aee38a 100644 --- a/store/src/main/java/org/apache/rocketmq/store/PutMessageResult.java +++ b/store/src/main/java/org/apache/rocketmq/store/PutMessageResult.java @@ -19,14 +19,28 @@ 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() { - return this.appendMessageResult != null && this.appendMessageResult.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() { @@ -45,10 +59,18 @@ 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 + "]"; + + appendMessageResult + ", remotePut=" + remotePut + "]"; } } diff --git a/store/src/main/java/org/apache/rocketmq/store/PutMessageStatus.java b/store/src/main/java/org/apache/rocketmq/store/PutMessageStatus.java index 29d0d95d9af..55afd37320d 100644 --- a/store/src/main/java/org/apache/rocketmq/store/PutMessageStatus.java +++ b/store/src/main/java/org/apache/rocketmq/store/PutMessageStatus.java @@ -22,10 +22,15 @@ public enum PutMessageStatus { FLUSH_SLAVE_TIMEOUT, SLAVE_NOT_AVAILABLE, SERVICE_NOT_AVAILABLE, - CREATE_MAPEDFILE_FAILED, + CREATE_MAPPED_FILE_FAILED, MESSAGE_ILLEGAL, PROPERTIES_SIZE_EXCEEDED, - OS_PAGECACHE_BUSY, + 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 } diff --git a/store/src/main/java/org/apache/rocketmq/store/QueryMessageResult.java b/store/src/main/java/org/apache/rocketmq/store/QueryMessageResult.java index a7a68508162..fbcbc05acf1 100644 --- a/store/src/main/java/org/apache/rocketmq/store/QueryMessageResult.java +++ b/store/src/main/java/org/apache/rocketmq/store/QueryMessageResult.java @@ -23,9 +23,9 @@ public class QueryMessageResult { private final List messageMapedList = - new ArrayList(100); + new ArrayList<>(100); - private final List messageBufferList = new ArrayList(100); + private final List messageBufferList = new ArrayList<>(100); private long indexLastUpdateTimestamp; private long indexLastUpdatePhyoffset; @@ -66,4 +66,8 @@ public List getMessageBufferList() { public int getBufferTotalSize() { return bufferTotalSize; } + + public List getMessageMapedList() { + return messageMapedList; + } } diff --git a/store/src/main/java/org/apache/rocketmq/store/RunningFlags.java b/store/src/main/java/org/apache/rocketmq/store/RunningFlags.java index 7ff11a282a3..2ae6879aadb 100644 --- a/store/src/main/java/org/apache/rocketmq/store/RunningFlags.java +++ b/store/src/main/java/org/apache/rocketmq/store/RunningFlags.java @@ -28,6 +28,8 @@ public class RunningFlags { private static final int DISK_FULL_BIT = 1 << 4; + private static final int FENCED_BIT = 1 << 5; + private volatile int flagBits = 0; public RunningFlags() { @@ -46,11 +48,11 @@ public boolean getAndMakeReadable() { } public boolean isReadable() { - if ((this.flagBits & NOT_READABLE_BIT) == 0) { - return true; - } + return (this.flagBits & NOT_READABLE_BIT) == 0; + } - return false; + public boolean isFenced() { + return (this.flagBits & FENCED_BIT) != 0; } public boolean getAndMakeNotReadable() { @@ -70,7 +72,7 @@ public boolean getAndMakeWriteable() { } public boolean isWriteable() { - if ((this.flagBits & (NOT_WRITEABLE_BIT | WRITE_LOGICS_QUEUE_ERROR_BIT | DISK_FULL_BIT | WRITE_INDEX_FILE_ERROR_BIT)) == 0) { + if ((this.flagBits & (NOT_WRITEABLE_BIT | WRITE_LOGICS_QUEUE_ERROR_BIT | DISK_FULL_BIT | WRITE_INDEX_FILE_ERROR_BIT | FENCED_BIT)) == 0) { return true; } @@ -98,6 +100,14 @@ 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; diff --git a/store/src/main/java/org/apache/rocketmq/store/SelectMappedBufferResult.java b/store/src/main/java/org/apache/rocketmq/store/SelectMappedBufferResult.java index 03061e6b48a..5c38cfe92a9 100644 --- a/store/src/main/java/org/apache/rocketmq/store/SelectMappedBufferResult.java +++ b/store/src/main/java/org/apache/rocketmq/store/SelectMappedBufferResult.java @@ -17,6 +17,7 @@ package org.apache.rocketmq.store; import java.nio.ByteBuffer; +import org.apache.rocketmq.store.logfile.MappedFile; public class SelectMappedBufferResult { @@ -26,7 +27,9 @@ public class SelectMappedBufferResult { private int size; - private MappedFile mappedFile; + protected MappedFile mappedFile; + + private boolean isInCache = true; public SelectMappedBufferResult(long startOffset, ByteBuffer byteBuffer, int size, MappedFile mappedFile) { this.startOffset = startOffset; @@ -48,14 +51,37 @@ public void setSize(final int 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; + } } diff --git a/store/src/main/java/org/apache/rocketmq/store/SelectMappedFileResult.java b/store/src/main/java/org/apache/rocketmq/store/SelectMappedFileResult.java new file mode 100644 index 00000000000..9655f281cc3 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/SelectMappedFileResult.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/StoreCheckpoint.java b/store/src/main/java/org/apache/rocketmq/store/StoreCheckpoint.java index 7e6c706942b..1e2504a2be0 100644 --- a/store/src/main/java/org/apache/rocketmq/store/StoreCheckpoint.java +++ b/store/src/main/java/org/apache/rocketmq/store/StoreCheckpoint.java @@ -24,32 +24,37 @@ import java.nio.channels.FileChannel.MapMode; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + 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 physicMsgTimestamp = 0; private volatile long logicsMsgTimestamp = 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); - MappedFile.ensureDirOK(file.getParent()); + 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, MappedFile.OS_PAGE_SIZE); + 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); log.info("store checkpoint file physicMsgTimestamp " + this.physicMsgTimestamp + ", " + UtilAll.timeMillisToHumanString(this.physicMsgTimestamp)); @@ -57,6 +62,8 @@ public StoreCheckpoint(final String scpPath) throws IOException { + 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); } else { log.info("store checkpoint file not exists, " + scpPath); } @@ -66,7 +73,7 @@ public void shutdown() { this.flush(); // unmap mappedByteBuffer - MappedFile.clean(this.mappedByteBuffer); + UtilAll.cleanBuffer(this.mappedByteBuffer); try { this.fileChannel.close(); @@ -79,6 +86,8 @@ public void flush() { 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.force(); } @@ -98,6 +107,14 @@ public void setLogicsMsgTimestamp(long logicsMsgTimestamp) { this.logicsMsgTimestamp = logicsMsgTimestamp; } + public long getConfirmPhyOffset() { + return confirmPhyOffset; + } + + public void setConfirmPhyOffset(long confirmPhyOffset) { + this.confirmPhyOffset = confirmPhyOffset; + } + public long getMinTimestampIndex() { return Math.min(this.getMinTimestamp(), this.indexMsgTimestamp); } @@ -106,8 +123,9 @@ public long getMinTimestamp() { long min = Math.min(this.physicMsgTimestamp, this.logicsMsgTimestamp); min -= 1000 * 3; - if (min < 0) + if (min < 0) { min = 0; + } return min; } @@ -120,4 +138,11 @@ public void setIndexMsgTimestamp(long indexMsgTimestamp) { this.indexMsgTimestamp = indexMsgTimestamp; } + public long getMasterFlushedOffset() { + return masterFlushedOffset; + } + + public void setMasterFlushedOffset(long masterFlushedOffset) { + this.masterFlushedOffset = masterFlushedOffset; + } } diff --git a/store/src/main/java/org/apache/rocketmq/store/StoreStatsService.java b/store/src/main/java/org/apache/rocketmq/store/StoreStatsService.java index 7bd1a23758b..1969b146aa6 100644 --- a/store/src/main/java/org/apache/rocketmq/store/StoreStatsService.java +++ b/store/src/main/java/org/apache/rocketmq/store/StoreStatsService.java @@ -17,20 +17,25 @@ 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.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; public class StoreStatsService extends ServiceThread { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private static final int FREQUENCY_OF_SAMPLING = 1000; @@ -39,6 +44,12 @@ public class StoreStatsService extends ServiceThread { "[<=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(); @@ -49,13 +60,13 @@ public class StoreStatsService extends ServiceThread { new ConcurrentHashMap<>(128); private final LongAdder getMessageTimesTotalFound = new LongAdder(); - private final LongAdder getMessageTransferedMsgCount = new LongAdder(); + private final LongAdder getMessageTransferredMsgCount = new LongAdder(); private final LongAdder getMessageTimesTotalMiss = new LongAdder(); - private final LinkedList putTimesList = new LinkedList(); + private final LinkedList putTimesList = new LinkedList<>(); - private final LinkedList getTimesFoundList = new LinkedList(); - private final LinkedList getTimesMissList = new LinkedList(); - private final LinkedList transferedMsgCountList = 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(); @@ -71,11 +82,75 @@ public class StoreStatsService extends ServiceThread { private ReentrantLock samplingLock = new ReentrantLock(); private long lastPrintTimestamp = System.currentTimeMillis(); + private BrokerIdentity brokerIdentity; + + public StoreStatsService(BrokerIdentity brokerIdentity) { + this(); + this.brokerIdentity = brokerIdentity; + } + public StoreStatsService() { - this.initPutMessageDistributeTime(); + 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); + } } - private LongAdder[] initPutMessageDistributeTime() { + 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(); @@ -93,6 +168,7 @@ public long getPutMessageEntireTimeMax() { } public void setPutMessageEntireTimeMax(long value) { + this.incPutMessageEntireTime(value); final LongAdder[] times = this.putMessageDistributeTime; if (null == times) @@ -189,7 +265,7 @@ public String toString() { sb.append("\tgetFoundTps: " + this.getGetFoundTps() + "\r\n"); sb.append("\tgetMissTps: " + this.getGetMissTps() + "\r\n"); sb.append("\tgetTotalTps: " + this.getGetTotalTps() + "\r\n"); - sb.append("\tgetTransferedTps: " + this.getGetTransferedTps() + "\r\n"); + sb.append("\tgetTransferredTps: " + this.getGetTransferredTps() + "\r\n"); return sb.toString(); } @@ -285,16 +361,16 @@ private String getGetTotalTps() { return sb.toString(); } - private String getGetTransferedTps() { + private String getGetTransferredTps() { StringBuilder sb = new StringBuilder(); - sb.append(this.getGetTransferedTps(10)); + sb.append(this.getGetTransferredTps(10)); sb.append(" "); - sb.append(this.getGetTransferedTps(60)); + sb.append(this.getGetTransferredTps(60)); sb.append(" "); - sb.append(this.getGetTransferedTps(600)); + sb.append(this.getGetTransferredTps(600)); return sb.toString(); } @@ -399,15 +475,15 @@ private String getGetTotalTps(int time) { return Double.toString(found + miss); } - private String getGetTransferedTps(int time) { + private String getGetTransferredTps(int time) { String result = ""; this.samplingLock.lock(); try { - CallSnapshot last = this.transferedMsgCountList.getLast(); + CallSnapshot last = this.transferredMsgCountList.getLast(); - if (this.transferedMsgCountList.size() > time) { + if (this.transferredMsgCountList.size() > time) { CallSnapshot lastBefore = - this.transferedMsgCountList.get(this.transferedMsgCountList.size() - (time + 1)); + this.transferredMsgCountList.get(this.transferredMsgCountList.size() - (time + 1)); result += CallSnapshot.getTPS(lastBefore, last); } @@ -419,7 +495,7 @@ private String getGetTransferedTps(int time) { } public HashMap getRuntimeInfo() { - HashMap result = new HashMap(64); + HashMap result = new HashMap<>(64); Long totalTimes = getPutMessageTimesTotal(); if (0 == totalTimes) { @@ -442,7 +518,9 @@ public HashMap getRuntimeInfo() { result.put("getFoundTps", this.getGetFoundTps()); result.put("getMissTps", this.getGetMissTps()); result.put("getTotalTps", this.getGetTotalTps()); - result.put("getTransferedTps", this.getGetTransferedTps()); + 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; } @@ -467,6 +545,9 @@ public void run() { @Override public String getServiceName() { + if (this.brokerIdentity != null && this.brokerIdentity.isInBrokerContainer()) { + return brokerIdentity.getIdentifier() + StoreStatsService.class.getSimpleName(); + } return StoreStatsService.class.getSimpleName(); } @@ -490,10 +571,10 @@ private void sampling() { this.getTimesMissList.removeFirst(); } - this.transferedMsgCountList.add(new CallSnapshot(System.currentTimeMillis(), - this.getMessageTransferedMsgCount.longValue())); - if (this.transferedMsgCountList.size() > (MAX_RECORDS_OF_SAMPLING + 1)) { - this.transferedMsgCountList.removeFirst(); + this.transferredMsgCountList.add(new CallSnapshot(System.currentTimeMillis(), + this.getMessageTransferredMsgCount.longValue())); + if (this.transferredMsgCountList.size() > (MAX_RECORDS_OF_SAMPLING + 1)) { + this.transferredMsgCountList.removeFirst(); } } finally { @@ -505,14 +586,14 @@ 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_transfered_tps {}", + log.info("[STORETPS] put_tps {} get_found_tps {} get_miss_tps {} get_transferred_tps {}", this.getPutTps(printTPSInterval), this.getGetFoundTps(printTPSInterval), this.getGetMissTps(printTPSInterval), - this.getGetTransferedTps(printTPSInterval) + this.getGetTransferredTps(printTPSInterval) ); - final LongAdder[] times = this.initPutMessageDistributeTime(); + final LongAdder[] times = this.resetPutMessageDistributeTime(); if (null == times) return; @@ -524,7 +605,9 @@ private void printTps() { 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()); } } @@ -537,8 +620,8 @@ public LongAdder getGetMessageTimesTotalMiss() { return getMessageTimesTotalMiss; } - public LongAdder getGetMessageTransferedMsgCount() { - return getMessageTransferedMsgCount; + public LongAdder getGetMessageTransferredMsgCount() { + return getMessageTransferredMsgCount; } public LongAdder getPutMessageFailedTimes() { diff --git a/store/src/main/java/org/apache/rocketmq/store/StoreUtil.java b/store/src/main/java/org/apache/rocketmq/store/StoreUtil.java index f63efd6e982..526ca9bf1b0 100644 --- a/store/src/main/java/org/apache/rocketmq/store/StoreUtil.java +++ b/store/src/main/java/org/apache/rocketmq/store/StoreUtil.java @@ -16,10 +16,21 @@ */ package org.apache.rocketmq.store; +import com.google.common.base.Preconditions; +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.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") @@ -32,4 +43,37 @@ public static long 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(); + } } diff --git a/store/src/main/java/org/apache/rocketmq/store/Swappable.java b/store/src/main/java/org/apache/rocketmq/store/Swappable.java new file mode 100644 index 00000000000..cb8dee5e6b4 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/Swappable.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/TopicQueueLock.java b/store/src/main/java/org/apache/rocketmq/store/TopicQueueLock.java new file mode 100644 index 00000000000..a78eeed230a --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/TopicQueueLock.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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(); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/TransientStorePool.java b/store/src/main/java/org/apache/rocketmq/store/TransientStorePool.java index f692a99b1cc..8c1a5338b98 100644 --- a/store/src/main/java/org/apache/rocketmq/store/TransientStorePool.java +++ b/store/src/main/java/org/apache/rocketmq/store/TransientStorePool.java @@ -22,24 +22,24 @@ import java.util.Deque; import java.util.concurrent.ConcurrentLinkedDeque; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.store.config.MessageStoreConfig; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.util.LibC; import sun.nio.ch.DirectBuffer; public class TransientStorePool { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); private final int poolSize; private final int fileSize; private final Deque availableBuffers; - private final MessageStoreConfig storeConfig; + private final DefaultMessageStore messageStore; + private volatile boolean isRealCommit = true; - public TransientStorePool(final MessageStoreConfig storeConfig) { - this.storeConfig = storeConfig; - this.poolSize = storeConfig.getTransientStorePoolSize(); - this.fileSize = storeConfig.getMappedFileSizeCommitLog(); + public TransientStorePool(final DefaultMessageStore messageStore) { + this.messageStore = messageStore; + this.poolSize = messageStore.getMessageStoreConfig().getTransientStorePoolSize(); + this.fileSize = messageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); this.availableBuffers = new ConcurrentLinkedDeque<>(); } @@ -81,9 +81,17 @@ public ByteBuffer borrowBuffer() { } public int availableBufferNums() { - if (storeConfig.isTransientStorePoolEnable()) { + if (messageStore.isTransientStorePoolEnable()) { return availableBuffers.size(); } return Integer.MAX_VALUE; } + + public boolean isRealCommit() { + return isRealCommit; + } + + public void setRealCommit(boolean realCommit) { + isRealCommit = realCommit; + } } diff --git a/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java b/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java index 45293e637b5..d7b7b8c0873 100644 --- a/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java +++ b/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java @@ -20,6 +20,7 @@ import org.apache.rocketmq.common.annotation.ImportantField; import org.apache.rocketmq.store.ConsumeQueue; +import org.apache.rocketmq.store.queue.BatchConsumeQueue; public class MessageStoreConfig { @@ -31,19 +32,83 @@ public class MessageStoreConfig { //The directory in which the commitlog is kept @ImportantField - private String storePathCommitLog = System.getProperty("user.home") + File.separator + "store" - + File.separator + "commitlog"; + 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; + + // CompactinLog 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 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 int timerCongestNumEachSlot = Integer.MAX_VALUE; + + private int timerMetricSmallThreshold = 1000000; + private int timerProgressLogIntervalMs = 10 * 1000; + // 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; @@ -58,6 +123,12 @@ public class MessageStoreConfig { @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.
    */ @@ -83,9 +154,11 @@ public class MessageStoreConfig { // 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,default is 4M + // The maximum size of message body,default is 4M,4M only for body length,not include others. private int maxMessageSize = 1024 * 1024 * 4; // Whether check the CRC32 of the records consumed. // This ensures no on-the-wire or on-disk corruption to the messages occurred. @@ -122,15 +195,22 @@ public class MessageStoreConfig { 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 haSlaveFallbehindMax = 1024 * 1024 * 256; + private int haMaxGapNotInSync = 1024 * 1024 * 256; @ImportantField - private BrokerRole brokerRole = BrokerRole.ASYNC_MASTER; + 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; @@ -149,14 +229,13 @@ public class MessageStoreConfig { private int transientStorePoolSize = 5; private boolean fastFailIfNoBufferInStorePool = false; + // DLedger message store config private boolean enableDLegerCommitLog = false; private String dLegerGroup; private String dLegerPeers; private String dLegerSelfId; - private String preferredLeaderId; - - private boolean isEnableBatchPush = false; + private boolean enableBatchPush = false; private boolean enableScheduleMessageStats = true; @@ -168,6 +247,151 @@ public class MessageStoreConfig { 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; + + 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 = false; + 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; + public boolean isDebugLockEnable() { return debugLockEnable; } @@ -208,6 +432,54 @@ 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; } @@ -290,6 +562,48 @@ public void setMaxMessageSize(int maxMessageSize) { this.maxMessageSize = maxMessageSize; } + @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; } @@ -303,6 +617,9 @@ public void setCheckCRCOnRecover(boolean checkCRCOnRecover) { } public String getStorePathCommitLog() { + if (storePathCommitLog == null) { + return storePathRootDir + File.separator + "commitlog"; + } return storePathCommitLog; } @@ -310,6 +627,36 @@ 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; } @@ -481,6 +828,10 @@ public int getHaListenPort() { } public void setHaListenPort(int haListenPort) { + if (haListenPort < 0) { + this.haListenPort = 0; + return; + } this.haListenPort = haListenPort; } @@ -520,12 +871,12 @@ public void setHaTransferBatchSize(int haTransferBatchSize) { this.haTransferBatchSize = haTransferBatchSize; } - public int getHaSlaveFallbehindMax() { - return haSlaveFallbehindMax; + public int getHaMaxGapNotInSync() { + return haMaxGapNotInSync; } - public void setHaSlaveFallbehindMax(int haSlaveFallbehindMax) { - this.haSlaveFallbehindMax = haSlaveFallbehindMax; + public void setHaMaxGapNotInSync(int haMaxGapNotInSync) { + this.haMaxGapNotInSync = haMaxGapNotInSync; } public FlushDiskType getFlushDiskType() { @@ -548,6 +899,14 @@ 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; } @@ -636,15 +995,8 @@ public void setDefaultQueryMaxNum(int defaultQueryMaxNum) { this.defaultQueryMaxNum = defaultQueryMaxNum; } - /** - * Enable transient commitLog store pool only if transientStorePoolEnable is true and the FlushDiskType is - * ASYNC_FLUSH - * - * @return true or false - */ public boolean isTransientStorePoolEnable() { - return transientStorePoolEnable && FlushDiskType.ASYNC_FLUSH == getFlushDiskType() - && BrokerRole.SLAVE != getBrokerRole(); + return transientStorePoolEnable; } public void setTransientStorePoolEnable(final boolean transientStorePoolEnable) { @@ -699,6 +1051,38 @@ public void setCommitCommitLogThoroughInterval(final int commitCommitLogThorough 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; } @@ -706,6 +1090,7 @@ public String getReadOnlyCommitLogStorePaths() { public void setReadOnlyCommitLogStorePaths(String readOnlyCommitLogStorePaths) { this.readOnlyCommitLogStorePaths = readOnlyCommitLogStorePaths; } + public String getdLegerGroup() { return dLegerGroup; } @@ -747,11 +1132,11 @@ public void setPreferredLeaderId(String preferredLeaderId) { } public boolean isEnableBatchPush() { - return isEnableBatchPush; + return enableBatchPush; } public void setEnableBatchPush(boolean enableBatchPush) { - isEnableBatchPush = enableBatchPush; + this.enableBatchPush = enableBatchPush; } public boolean isEnableScheduleMessageStats() { @@ -762,6 +1147,294 @@ 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; } @@ -809,4 +1482,232 @@ public int getScheduleAsyncDeliverMaxResendNum2Blocked() { 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 boolean isTimerWarmEnable() { + return timerWarmEnable; + } + + 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 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; + } } diff --git a/store/src/main/java/org/apache/rocketmq/store/config/StorePathConfigHelper.java b/store/src/main/java/org/apache/rocketmq/store/config/StorePathConfigHelper.java index ccd76c4f092..2f34e7dff54 100644 --- a/store/src/main/java/org/apache/rocketmq/store/config/StorePathConfigHelper.java +++ b/store/src/main/java/org/apache/rocketmq/store/config/StorePathConfigHelper.java @@ -27,6 +27,9 @@ public static String getStorePathConsumeQueue(final String rootDir) { 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"; diff --git a/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java b/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java index 12b8ec7f878..ec5e86d704d 100644 --- a/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java +++ b/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java @@ -33,34 +33,37 @@ import java.net.Inet6Address; import java.net.InetSocketAddress; import java.nio.ByteBuffer; -import java.util.HashMap; 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.MessageAccessor; -import org.apache.rocketmq.common.message.MessageConst; 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.common.topic.TopicValidator; 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.MappedFile; -import org.apache.rocketmq.store.MessageExtBrokerInner; +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.schedule.ScheduleMessageService; +import org.apache.rocketmq.store.config.MessageStoreConfig; +import org.apache.rocketmq.store.logfile.MappedFile; /** * 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; @@ -88,11 +91,14 @@ public DLedgerCommitLog(final DefaultMessageStore defaultMessageStore) { 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); @@ -258,6 +264,23 @@ public SelectMappedBufferResult getData(final long offset, final boolean returnF 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 recover(long maxPhyOffsetOfConsumeQueue) { dLedgerFileStore.load(); @@ -322,16 +345,11 @@ public void recoverAbnormally(long maxPhyOffsetOfConsumeQueue) { recover(maxPhyOffsetOfConsumeQueue); } - @Override - public DispatchRequest checkMessageAndReturnSize(ByteBuffer byteBuffer, final boolean checkCRC) { - return this.checkMessageAndReturnSize(byteBuffer, checkCRC, true); - } - @Override public DispatchRequest checkMessageAndReturnSize(ByteBuffer byteBuffer, final boolean checkCRC, - final boolean readBody) { + final boolean checkDupInfo, final boolean readBody) { if (isInrecoveringOldCommitlog) { - return super.checkMessageAndReturnSize(byteBuffer, checkCRC, readBody); + return super.checkMessageAndReturnSize(byteBuffer, checkCRC, checkDupInfo, readBody); } try { int bodyOffset = DLedgerEntry.BODY_OFFSET; @@ -339,15 +357,17 @@ public DispatchRequest checkMessageAndReturnSize(ByteBuffer byteBuffer, final bo 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 == CommitLog.MESSAGE_MAGIC_CODE) { + 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, readBody); + 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, readBody); + DispatchRequest dispatchRequest = super.checkMessageAndReturnSize(byteBuffer, checkCRC, checkDupInfo, readBody); if (dispatchRequest.isSuccess()) { dispatchRequest.setBufferSize(dispatchRequest.getMsgSize() + bodyOffset); } else if (dispatchRequest.getMsgSize() > 0) { @@ -378,29 +398,6 @@ private void setMessageInfo(MessageExtBrokerInner msg, int tranType) { // on the client) msg.setBodyCRC(UtilAll.crc32(msg.getBody())); - //should be consistent with the old version - if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE - || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) { - // Delay Delivery - if (msg.getDelayTimeLevel() > 0) { - if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) { - msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()); - } - - - String topic = TopicValidator.RMQ_SYS_SCHEDULE_TOPIC; - int queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel()); - - // 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(topic); - msg.setQueueId(queueId); - } - } - InetSocketAddress bornSocketAddress = (InetSocketAddress) msg.getBornHost(); if (bornSocketAddress.getAddress() instanceof Inet6Address) { msg.setBornHostV6Flag(); @@ -423,60 +420,65 @@ public CompletableFuture asyncPutMessage(MessageExtBrokerInner 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))); - } - putMessageLock.lock(); //spin or ReentrantLock ,depending on store config - long elapsedTimeInLock; - long queueOffset; + String topicQueueKey = msg.getTopic() + "-" + msg.getQueueId(); + topicQueueLock.lock(topicQueueKey); try { - beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); - queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, 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_PAGECACHE_BUSY, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR))); - } - long wroteOffset = dledgerFuture.getPos() + DLedgerEntry.BODY_OFFSET; + defaultMessageStore.assignOffset(msg); - int msgIdLength = (msg.getSysFlag() & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 4 + 4 + 8 : 16 + 4 + 8; - ByteBuffer buffer = ByteBuffer.allocate(msgIdLength); + encodeResult = this.messageSerializer.serialize(msg); + if (encodeResult.status != AppendMessageStatus.PUT_OK) { + return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult.status))); + } + 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); + } catch (Exception e) { + log.error("Put message error", e); + return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR))); + } finally { + beginTimeInDledgerLock = 0; + putMessageLock.unlock(); + } - 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); - switch (tranType) { - case MessageSysFlag.TRANSACTION_PREPARED_TYPE: - case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: - break; - case MessageSysFlag.TRANSACTION_NOT_TYPE: - case MessageSysFlag.TRANSACTION_COMMIT_TYPE: - // The next update ConsumeQueue information - DLedgerCommitLog.this.topicQueueTable.put(encodeResult.queueOffsetKey, queueOffset + 1); - break; - default: - break; + if (elapsedTimeInLock > 500) { + log.warn("[NOTIFYME]putMessage in lock cost time(ms)={}, bodyLength={} AppendMessageResult={}", elapsedTimeInLock, msg.getBody().length, appendResult); } - } catch (Exception e) { - log.error("Put message error", e); - return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR))); - } 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)); + } finally { + topicQueueLock.unlock(topicQueueKey); } return dledgerFuture.thenApply(appendEntryResponse -> { @@ -492,11 +494,11 @@ public CompletableFuture asyncPutMessage(MessageExtBrokerInner putMessageStatus = PutMessageStatus.SERVICE_NOT_AVAILABLE; break; case WAIT_QUORUM_ACK_TIMEOUT: - //Do not return flush_slave_timeout to the client, for the ons client will ignore it. - putMessageStatus = PutMessageStatus.OS_PAGECACHE_BUSY; + //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_PAGECACHE_BUSY; + putMessageStatus = PutMessageStatus.OS_PAGE_CACHE_BUSY; break; } PutMessageResult putMessageResult = new PutMessageResult(putMessageStatus, appendResult); @@ -535,6 +537,13 @@ public CompletableFuture asyncPutMessages(MessageExtBatch mess 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; @@ -543,67 +552,77 @@ public CompletableFuture asyncPutMessages(MessageExtBatch mess encodeResult = this.messageSerializer.serialize(messageExtBatch); if (encodeResult.status != AppendMessageStatus.PUT_OK) { return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult - .status))); + .status))); } - putMessageLock.lock(); //spin or ReentrantLock ,depending on store config - msgIdBuilder.setLength(0); - long elapsedTimeInLock; - long queueOffset; - int msgNum = 0; + int batchNum = encodeResult.batchData.size(); + topicQueueLock.lock(encodeResult.queueOffsetKey); try { - beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); - queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, 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_PAGECACHE_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; + 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))); } - String msgId = MessageDecoder.createMessageId(buffer, messageExtBatch.getStoreHostBytes(), wroteOffset); - if (msgIdBuilder.length() > 0) { - msgIdBuilder.append(',').append(msgId); - } else { - msgIdBuilder.append(msgId); + 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++; } - msgNum++; - } - elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginTimeInDledgerLock; - appendResult = new AppendMessageResult(AppendMessageStatus.PUT_OK, firstWroteOffset, encodeResult.totalMsgLen, + elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginTimeInDledgerLock; + appendResult = new AppendMessageResult(AppendMessageStatus.PUT_OK, firstWroteOffset, encodeResult.totalMsgLen, msgIdBuilder.toString(), System.currentTimeMillis(), queueOffset, elapsedTimeInLock); - appendResult.setMsgNum(msgNum); - DLedgerCommitLog.this.topicQueueTable.put(encodeResult.queueOffsetKey, queueOffset + msgNum); - } catch (Exception e) { - log.error("Put message error", e); - return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR))); - } finally { - beginTimeInDledgerLock = 0; - putMessageLock.unlock(); - } + appendResult.setMsgNum(msgNum); + } catch (Exception e) { + log.error("Put message error", e); + return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR))); + } finally { + beginTimeInDledgerLock = 0; + putMessageLock.unlock(); + } - if (elapsedTimeInLock > 500) { - log.warn("[NOTIFYME]putMessage in lock cost time(ms)={}, bodyLength={} AppendMessageResult={}", + if (elapsedTimeInLock > 500) { + log.warn("[NOTIFYME]putMessage in lock cost time(ms)={}, bodyLength={} AppendMessageResult={}", elapsedTimeInLock, messageExtBatch.getBody().length, appendResult); + } + + defaultMessageStore.increaseOffset(messageExtBatch, (short) batchNum); + + } finally { + topicQueueLock.unlock(encodeResult.queueOffsetKey); } return dledgerFuture.thenApply(appendEntryResponse -> { @@ -619,11 +638,11 @@ public CompletableFuture asyncPutMessages(MessageExtBatch mess putMessageStatus = PutMessageStatus.SERVICE_NOT_AVAILABLE; break; case WAIT_QUORUM_ACK_TIMEOUT: - //Do not return flush_slave_timeout to the client, for the ons client will ignore it. - putMessageStatus = PutMessageStatus.OS_PAGECACHE_BUSY; + //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_PAGECACHE_BUSY; + putMessageStatus = PutMessageStatus.OS_PAGE_CACHE_BUSY; break; } PutMessageResult putMessageResult = new PutMessageResult(putMessageStatus, appendResult); @@ -656,16 +675,6 @@ public long rollNextFile(final long offset) { return offset + mappedFileSize - offset % mappedFileSize; } - @Override - public HashMap getTopicQueueTable() { - return topicQueueTable; - } - - @Override - public void setTopicQueueTable(HashMap topicQueueTable) { - this.topicQueueTable = topicQueueTable; - } - @Override public void destroy() { super.destroy(); @@ -698,12 +707,8 @@ public long lockTimeMills() { return diff; } - private long getQueueOffsetByKey(String key, int tranType) { - Long queueOffset = DLedgerCommitLog.this.topicQueueTable.get(key); - if (null == queueOffset) { - queueOffset = 0L; - DLedgerCommitLog.this.topicQueueTable.put(key, queueOffset); - } + private long getQueueOffsetByKey(MessageExtBrokerInner msg, int tranType) { + Long queueOffset = msg.getQueueOffset(); // Transaction messages that require special handling switch (tranType) { @@ -721,7 +726,6 @@ private long getQueueOffsetByKey(String key, int tranType) { return queueOffset; } - class EncodeResult { private String queueOffsetKey; private ByteBuffer data; @@ -750,7 +754,8 @@ public byte[] getData() { return data.array(); } - public EncodeResult(AppendMessageStatus status, String queueOffsetKey, List batchData, int totalMsgLen) { + public EncodeResult(AppendMessageStatus status, String queueOffsetKey, List batchData, + int totalMsgLen) { this.batchData = batchData; this.status = status; this.queueOffsetKey = queueOffsetKey; @@ -760,11 +765,11 @@ public EncodeResult(AppendMessageStatus status, String queueOffsetKey, List this.maxMessageSize) { - DLedgerCommitLog.log.warn("message size exceeded, msg total size: " + msgLen + ", msg body size: " + bodyLength - + ", maxMessageSize: " + this.maxMessageSize); + 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 @@ -817,7 +822,7 @@ public EncodeResult serialize(final MessageExtBrokerInner msgInner) { // 1 TOTALSIZE msgStoreItemMemory.putInt(msgLen); // 2 MAGICCODE - msgStoreItemMemory.putInt(DLedgerCommitLog.MESSAGE_MAGIC_CODE); + msgStoreItemMemory.putInt(msgInner.getVersion().getMagicCode()); // 3 BODYCRC msgStoreItemMemory.putInt(msgInner.getBodyCRC()); // 4 QUEUEID @@ -851,7 +856,7 @@ public EncodeResult serialize(final MessageExtBrokerInner msgInner) { msgStoreItemMemory.put(msgInner.getBody()); } // 16 TOPIC - msgStoreItemMemory.put((byte) topicLength); + msgInner.getVersion().putTopicLength(msgStoreItemMemory, topicLength); msgStoreItemMemory.put(topicData); // 17 PROPERTIES msgStoreItemMemory.putShort((short) propertiesLength); @@ -866,6 +871,14 @@ public EncodeResult serialize(final MessageExtBatch messageExtBatch) { 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(); @@ -897,29 +910,17 @@ public EncodeResult serialize(final MessageExtBatch messageExtBatch) { final int topicLength = topicData.length; - final int msgLen = calMsgLength(messageExtBatch.getSysFlag(), bodyLen, topicLength, propertiesLen); + final int msgLen = MessageExtEncoder.calMsgLength(messageExtBatch.getVersion(), messageExtBatch.getSysFlag(), bodyLen, topicLength, propertiesLen); ByteBuffer msgStoreItemMemory = ByteBuffer.allocate(msgLen); - // Exceeds the maximum message - if (msgLen > this.maxMessageSize) { - CommitLog.log.warn("message size exceeded, msg total size: " + msgLen + ", msg body size: " + - bodyLen - + ", maxMessageSize: " + this.maxMessageSize); - throw new RuntimeException("message size exceeded"); - } - totalMsgLen += msgLen; - // Determines whether there is sufficient free space - if (totalMsgLen > maxMessageSize) { - throw new RuntimeException("message size exceeded"); - } // Initialization of storage space this.resetByteBuffer(msgStoreItemMemory, msgLen); // 1 TOTALSIZE msgStoreItemMemory.putInt(msgLen); // 2 MAGICCODE - msgStoreItemMemory.putInt(DLedgerCommitLog.MESSAGE_MAGIC_CODE); + msgStoreItemMemory.putInt(messageExtBatch.getVersion().getMagicCode()); // 3 BODYCRC msgStoreItemMemory.putInt(bodyCrc); // 4 QUEUEID @@ -952,7 +953,7 @@ public EncodeResult serialize(final MessageExtBatch messageExtBatch) { msgStoreItemMemory.put(messagesByteBuff.array(), bodyPos, bodyLen); } // 16 TOPIC - msgStoreItemMemory.put((byte) topicLength); + messageExtBatch.getVersion().putTopicLength(msgStoreItemMemory, topicLength); msgStoreItemMemory.put(topicData); // 17 PROPERTIES msgStoreItemMemory.putShort(propertiesLen); diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAClient.java b/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAClient.java new file mode 100644 index 00000000000..530d295ae96 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAClient.java @@ -0,0 +1,411 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAConnection.java b/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAConnection.java new file mode 100644 index 00000000000..5dd24410ed6 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAConnection.java @@ -0,0 +1,476 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAService.java b/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAService.java new file mode 100644 index 00000000000..75e0afa4e89 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAService.java @@ -0,0 +1,380 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } + this.acceptSocketService.shutdown(true); + this.destroyConnections(); + this.groupTransferService.shutdown(); + 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); + conn.start(); + DefaultHAService.this.addConnection(conn); + } 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; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/FlowMonitor.java b/store/src/main/java/org/apache/rocketmq/store/ha/FlowMonitor.java new file mode 100644 index 00000000000..810f2865caf --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/FlowMonitor.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} \ No newline at end of file diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/GroupTransferService.java b/store/src/main/java/org/apache/rocketmq/store/ha/GroupTransferService.java new file mode 100644 index 00000000000..a75cae8ef0c --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/GroupTransferService.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/HAClient.java b/store/src/main/java/org/apache/rocketmq/store/ha/HAClient.java new file mode 100644 index 00000000000..0449e01aa69 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/HAClient.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/HAConnection.java b/store/src/main/java/org/apache/rocketmq/store/ha/HAConnection.java index 4c26971c078..8e1e922979f 100644 --- a/store/src/main/java/org/apache/rocketmq/store/ha/HAConnection.java +++ b/store/src/main/java/org/apache/rocketmq/store/ha/HAConnection.java @@ -14,388 +14,64 @@ * 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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.remoting.common.RemotingUtil; -import org.apache.rocketmq.remoting.netty.NettySystemConfig; -import org.apache.rocketmq.store.SelectMappedBufferResult; - -public class HAConnection { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); - private final HAService haService; - private final SocketChannel socketChannel; - private final String clientAddr; - private WriteSocketService writeSocketService; - private ReadSocketService readSocketService; - - private volatile long slaveRequestOffset = -1; - private volatile long slaveAckOffset = -1; - - public HAConnection(final HAService haService, final SocketChannel socketChannel) throws IOException { - this.haService = haService; - this.socketChannel = socketChannel; - this.clientAddr = 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(); - } - - public void start() { - this.readSocketService.start(); - this.writeSocketService.start(); - } - - public void shutdown() { - this.writeSocketService.shutdown(true); - this.readSocketService.shutdown(true); - this.close(); - } - - public void close() { - if (this.socketChannel != null) { - try { - this.socketChannel.close(); - } catch (IOException e) { - HAConnection.log.error("", e); - } - } - } - - public SocketChannel getSocketChannel() { - return socketChannel; - } - - 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 = RemotingUtil.openSelector(); - this.socketChannel = socketChannel; - this.socketChannel.register(this.selector, SelectionKey.OP_READ); - this.setDaemon(true); - } - - @Override - public void run() { - HAConnection.log.info(this.getServiceName() + " service started"); - - while (!this.isStopped()) { - try { - this.selector.select(1000); - boolean ok = this.processReadEvent(); - if (!ok) { - HAConnection.log.error("processReadEvent error"); - break; - } - - long interval = HAConnection.this.haService.getDefaultMessageStore().getSystemClock().now() - this.lastReadTimestamp; - if (interval > HAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig().getHaHousekeepingInterval()) { - log.warn("ha housekeeping, found this connection[" + HAConnection.this.clientAddr + "] expired, " + interval); - break; - } - } catch (Exception e) { - HAConnection.log.error(this.getServiceName() + " service has exception.", e); - break; - } - } - - this.makeStop(); - - writeSocketService.makeStop(); - - haService.removeConnection(HAConnection.this); - - HAConnection.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) { - HAConnection.log.error("", e); - } - - HAConnection.log.info(this.getServiceName() + " service end"); - } - - @Override - public String getServiceName() { - 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 = HAConnection.this.haService.getDefaultMessageStore().getSystemClock().now(); - if ((this.byteBufferRead.position() - this.processPosition) >= 8) { - int pos = this.byteBufferRead.position() - (this.byteBufferRead.position() % 8); - long readOffset = this.byteBufferRead.getLong(pos - 8); - this.processPosition = pos; - - HAConnection.this.slaveAckOffset = readOffset; - if (HAConnection.this.slaveRequestOffset < 0) { - HAConnection.this.slaveRequestOffset = readOffset; - log.info("slave[" + HAConnection.this.clientAddr + "] request offset " + readOffset); - } else if (HAConnection.this.slaveAckOffset > HAConnection.this.haService.getDefaultMessageStore().getMaxPhyOffset()) { - log.warn("slave[{}] request offset={} greater than local commitLog offset={}. ", - HAConnection.this.clientAddr, - HAConnection.this.slaveAckOffset, - HAConnection.this.haService.getDefaultMessageStore().getMaxPhyOffset()); - return false; - } - - HAConnection.this.haService.notifyTransferSome(HAConnection.this.slaveAckOffset); - } - } else if (readSize == 0) { - if (++readSizeZeroTimes >= 3) { - break; - } - } else { - log.error("read socket[" + HAConnection.this.clientAddr + "] < 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 int headerSize = 8 + 4; - private final ByteBuffer byteBufferHeader = ByteBuffer.allocate(headerSize); - private long nextTransferFromWhere = -1; - private SelectMappedBufferResult selectMappedBufferResult; - private boolean lastWriteOver = true; - private long lastWriteTimestamp = System.currentTimeMillis(); - - public WriteSocketService(final SocketChannel socketChannel) throws IOException { - this.selector = RemotingUtil.openSelector(); - this.socketChannel = socketChannel; - this.socketChannel.register(this.selector, SelectionKey.OP_WRITE); - this.setDaemon(true); - } - - @Override - public void run() { - HAConnection.log.info(this.getServiceName() + " service started"); - - while (!this.isStopped()) { - try { - this.selector.select(1000); - - if (-1 == HAConnection.this.slaveRequestOffset) { - Thread.sleep(10); - continue; - } - - if (-1 == this.nextTransferFromWhere) { - if (0 == HAConnection.this.slaveRequestOffset) { - long masterOffset = HAConnection.this.haService.getDefaultMessageStore().getCommitLog().getMaxOffset(); - masterOffset = - masterOffset - - (masterOffset % HAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig() - .getMappedFileSizeCommitLog()); - - if (masterOffset < 0) { - masterOffset = 0; - } - - this.nextTransferFromWhere = masterOffset; - } else { - this.nextTransferFromWhere = HAConnection.this.slaveRequestOffset; - } - - log.info("master transfer data from " + this.nextTransferFromWhere + " to slave[" + HAConnection.this.clientAddr - + "], and slave request " + HAConnection.this.slaveRequestOffset); - } - - if (this.lastWriteOver) { - - long interval = - HAConnection.this.haService.getDefaultMessageStore().getSystemClock().now() - this.lastWriteTimestamp; - - if (interval > HAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig() - .getHaSendHeartbeatInterval()) { - - // Build Header - this.byteBufferHeader.position(0); - this.byteBufferHeader.limit(headerSize); - 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 = - HAConnection.this.haService.getDefaultMessageStore().getCommitLogData(this.nextTransferFromWhere); - if (selectResult != null) { - int size = selectResult.getSize(); - if (size > HAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig().getHaTransferBatchSize()) { - size = HAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig().getHaTransferBatchSize(); - } - - long thisOffset = this.nextTransferFromWhere; - this.nextTransferFromWhere += size; - - selectResult.getByteBuffer().limit(size); - this.selectMappedBufferResult = selectResult; - - // Build Header - this.byteBufferHeader.position(0); - this.byteBufferHeader.limit(headerSize); - this.byteBufferHeader.putLong(thisOffset); - this.byteBufferHeader.putInt(size); - this.byteBufferHeader.flip(); - - this.lastWriteOver = this.transferData(); - } else { - - HAConnection.this.haService.getWaitNotifyObject().allWaitForRunning(100); - } - } catch (Exception e) { - - HAConnection.log.error(this.getServiceName() + " service has exception.", e); - break; - } - } - - HAConnection.this.haService.getWaitNotifyObject().removeFromWaitingThreadTable(); - - if (this.selectMappedBufferResult != null) { - this.selectMappedBufferResult.release(); - } - - this.makeStop(); - - readSocketService.makeStop(); - - haService.removeConnection(HAConnection.this); - - SelectionKey sk = this.socketChannel.keyFor(this.selector); - if (sk != null) { - sk.cancel(); - } - - try { - this.selector.close(); - this.socketChannel.close(); - } catch (IOException e) { - HAConnection.log.error("", e); - } - - HAConnection.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) { - writeSizeZeroTimes = 0; - this.lastWriteTimestamp = HAConnection.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 = HAConnection.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() { - return WriteSocketService.class.getSimpleName(); - } - @Override - public void shutdown() { - super.shutdown(); - } - } +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(); } diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/HAConnectionState.java b/store/src/main/java/org/apache/rocketmq/store/ha/HAConnectionState.java new file mode 100644 index 00000000000..4f0c5ca9095 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/HAConnectionState.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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, +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/HAConnectionStateNotificationRequest.java b/store/src/main/java/org/apache/rocketmq/store/ha/HAConnectionStateNotificationRequest.java new file mode 100644 index 00000000000..8a3f6aabfc1 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/HAConnectionStateNotificationRequest.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/HAConnectionStateNotificationService.java b/store/src/main/java/org/apache/rocketmq/store/ha/HAConnectionStateNotificationService.java new file mode 100644 index 00000000000..197d9f6ba4f --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/HAConnectionStateNotificationService.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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"); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java b/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java index c9c05b308a7..467da603d47 100644 --- a/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java +++ b/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java @@ -14,613 +14,154 @@ * 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.ByteBuffer; -import java.nio.channels.ClosedChannelException; -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 java.util.concurrent.atomic.AtomicReference; -import org.apache.rocketmq.common.ServiceThread; -import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.remoting.common.RemotingUtil; +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.PutMessageSpinLock; -import org.apache.rocketmq.store.PutMessageStatus; - -public class HAService { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); - - private final AtomicInteger connectionCount = new AtomicInteger(0); - - private final List connectionList = new LinkedList<>(); - - private final AcceptSocketService acceptSocketService; +import org.apache.rocketmq.store.config.MessageStoreConfig; - private final DefaultMessageStore defaultMessageStore; +public interface HAService { - private final WaitNotifyObject waitNotifyObject = new WaitNotifyObject(); - private final AtomicLong push2SlaveMaxOffset = new AtomicLong(0); - - private final GroupTransferService groupTransferService; - - private final HAClient haClient; - - public HAService(final DefaultMessageStore defaultMessageStore) throws IOException { - this.defaultMessageStore = defaultMessageStore; - this.acceptSocketService = - new AcceptSocketService(defaultMessageStore.getMessageStoreConfig().getHaListenPort()); - this.groupTransferService = new GroupTransferService(); - this.haClient = new HAClient(); - } - - public void updateMasterAddress(final String newAddr) { - if (this.haClient != null) { - this.haClient.updateMasterAddress(newAddr); - } - } - - public void putRequest(final CommitLog.GroupCommitRequest request) { - this.groupTransferService.putRequest(request); - } - - public boolean isSlaveOK(final long masterPutWhere) { - boolean result = this.connectionCount.get() > 0; - result = - result - && ((masterPutWhere - this.push2SlaveMaxOffset.get()) < this.defaultMessageStore - .getMessageStoreConfig().getHaSlaveFallbehindMax()); - 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(); - } - } - } - - public AtomicInteger getConnectionCount() { - return connectionCount; - } - - // public void notifyTransferSome() { - // this.groupTransferService.notifyTransferSome(); - // } - - public void start() throws Exception { - this.acceptSocketService.beginAccept(); - this.acceptSocketService.start(); - this.groupTransferService.start(); - this.haClient.start(); - } - - public void addConnection(final HAConnection conn) { - synchronized (this.connectionList) { - this.connectionList.add(conn); - } - } - - public void removeConnection(final HAConnection conn) { - synchronized (this.connectionList) { - this.connectionList.remove(conn); - } - } - - public void shutdown() { - this.haClient.shutdown(); - this.acceptSocketService.shutdown(true); - this.destroyConnections(); - this.groupTransferService.shutdown(); - } - - public void destroyConnections() { - synchronized (this.connectionList) { - for (HAConnection c : this.connectionList) { - c.shutdown(); - } + /** + * Init HAService, must be called before other methods. + * + * @param defaultMessageStore + * @throws IOException + */ + void init(DefaultMessageStore defaultMessageStore) throws IOException; - this.connectionList.clear(); - } - } + /** + * Start HA Service + * + * @throws Exception + */ + void start() throws Exception; - public DefaultMessageStore getDefaultMessageStore() { - return defaultMessageStore; - } + /** + * Shutdown HA Service + */ + void shutdown(); - public WaitNotifyObject getWaitNotifyObject() { - return waitNotifyObject; + /** + * Change to master state + * + * @param masterEpoch the new masterEpoch + */ + default boolean changeToMaster(int masterEpoch) { + return false; } - public AtomicLong getPush2SlaveMaxOffset() { - return push2SlaveMaxOffset; + /** + * Change to master state + * + * @param masterEpoch the new masterEpoch + */ + default boolean changeToMasterWhenLastRoleIsMaster(int masterEpoch) { + return false; } /** - * Listens to slave connections to create {@link HAConnection}. + * Change to slave state + * + * @param newMasterAddr new master addr + * @param newMasterEpoch new masterEpoch */ - class AcceptSocketService extends ServiceThread { - private final SocketAddress socketAddressListen; - private ServerSocketChannel serverSocketChannel; - private Selector selector; - - public AcceptSocketService(final int port) { - this.socketAddressListen = new InetSocketAddress(port); - } - - /** - * Starts listening to slave connections. - * - * @throws Exception If fails. - */ - public void beginAccept() throws Exception { - this.serverSocketChannel = ServerSocketChannel.open(); - this.selector = RemotingUtil.openSelector(); - this.serverSocketChannel.socket().setReuseAddress(true); - this.serverSocketChannel.socket().bind(this.socketAddressListen); - this.serverSocketChannel.configureBlocking(false); - this.serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT); - } - - /** - * {@inheritDoc} - */ - @Override - public void shutdown(final boolean interrupt) { - super.shutdown(interrupt); - try { - this.serverSocketChannel.close(); - 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.readyOps() & SelectionKey.OP_ACCEPT) != 0) { - SocketChannel sc = ((ServerSocketChannel) k.channel()).accept(); - - if (sc != null) { - HAService.log.info("HAService receive new connection, " - + sc.socket().getRemoteSocketAddress()); - - try { - HAConnection conn = new HAConnection(HAService.this, sc); - conn.start(); - HAService.this.addConnection(conn); - } 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"); - } - - /** - * {@inheritDoc} - */ - @Override - public String getServiceName() { - return AcceptSocketService.class.getSimpleName(); - } + default boolean changeToSlave(String newMasterAddr, int newMasterEpoch, Long slaveId) { + return false; } /** - * GroupTransferService Service + * Change to slave state + * + * @param newMasterAddr new master addr + * @param newMasterEpoch new masterEpoch */ - class GroupTransferService extends ServiceThread { - - private final WaitNotifyObject notifyTransferObject = new WaitNotifyObject(); - private final PutMessageSpinLock lock = new PutMessageSpinLock(); - private volatile LinkedList requestsWrite = new LinkedList<>(); - private volatile LinkedList requestsRead = new LinkedList<>(); - - public void putRequest(final CommitLog.GroupCommitRequest request) { - lock.lock(); - try { - this.requestsWrite.add(request); - } finally { - lock.unlock(); - } - this.wakeup(); - } - - public void notifyTransferSome() { - this.notifyTransferObject.wakeup(); - } - - private void swapRequests() { - lock.lock(); - try { - LinkedList 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 = HAService.this.push2SlaveMaxOffset.get() >= req.getNextOffset(); - long deadLine = req.getDeadLine(); - while (!transferOK && deadLine - System.nanoTime() > 0) { - this.notifyTransferObject.waitForRunning(1000); - transferOK = HAService.this.push2SlaveMaxOffset.get() >= req.getNextOffset(); - } - - req.wakeupCustomer(transferOK ? PutMessageStatus.PUT_OK : PutMessageStatus.FLUSH_SLAVE_TIMEOUT); - } - - this.requestsRead = new LinkedList<>(); - } - } - - 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() { - return GroupTransferService.class.getSimpleName(); - } + default boolean changeToSlaveWhenMasterNotChange(String newMasterAddr, int newMasterEpoch) { + return false; } - class HAClient extends ServiceThread { - private static final int READ_MAX_BUFFER_SIZE = 1024 * 1024 * 4; - private final AtomicReference masterAddress = new AtomicReference<>(); - private final ByteBuffer reportOffset = ByteBuffer.allocate(8); - private SocketChannel socketChannel; - private Selector selector; - 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); - - public HAClient() throws IOException { - this.selector = RemotingUtil.openSelector(); - } - - public void updateMasterAddress(final String newAddr) { - String currentAddr = this.masterAddress.get(); - if (currentAddr == null || !currentAddr.equals(newAddr)) { - this.masterAddress.set(newAddr); - log.info("update master address, OLD: " + currentAddr + " NEW: " + newAddr); - } - } - - private boolean isTimeToReportOffset() { - long interval = - HAService.this.defaultMessageStore.getSystemClock().now() - this.lastWriteTimestamp; - boolean needHeart = interval > HAService.this.defaultMessageStore.getMessageStoreConfig() - .getHaSendHeartbeatInterval(); - - return needHeart; - } - - private boolean reportSlaveMaxOffset(final long maxOffset) { - this.reportOffset.position(0); - this.reportOffset.limit(8); - this.reportOffset.putLong(maxOffset); - this.reportOffset.position(0); - this.reportOffset.limit(8); - - 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 = HAService.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) { - readSizeZeroTimes = 0; - boolean result = this.dispatchReadRequest(); - if (!result) { - log.error("HAClient, dispatchReadRequest error"); - return false; - } - } 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() { - final int msgHeaderSize = 8 + 4; // phyoffset + size - - while (true) { - int diff = this.byteBufferRead.position() - this.dispatchPosition; - if (diff >= msgHeaderSize) { - long masterPhyOffset = this.byteBufferRead.getLong(this.dispatchPosition); - int bodySize = this.byteBufferRead.getInt(this.dispatchPosition + 8); - - long slavePhyOffset = HAService.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 >= (msgHeaderSize + bodySize)) { - byte[] bodyData = byteBufferRead.array(); - int dataStart = this.dispatchPosition + msgHeaderSize; - - HAService.this.defaultMessageStore.appendToCommitLog( - masterPhyOffset, bodyData, dataStart, bodySize); - - this.dispatchPosition += msgHeaderSize + bodySize; - - if (!reportSlaveMaxOffsetPlus()) { - return false; - } - - continue; - } - } - - if (!this.byteBufferRead.hasRemaining()) { - this.reallocateByteBuffer(); - } - - break; - } - - return true; - } - - private boolean reportSlaveMaxOffsetPlus() { - boolean result = true; - long currentPhyOffset = HAService.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; - } - - private boolean connectMaster() throws ClosedChannelException { - if (null == socketChannel) { - String addr = this.masterAddress.get(); - if (addr != null) { - - SocketAddress socketAddress = RemotingUtil.string2SocketAddress(addr); - if (socketAddress != null) { - this.socketChannel = RemotingUtil.connect(socketAddress); - if (this.socketChannel != null) { - this.socketChannel.register(this.selector, SelectionKey.OP_READ); - } - } - } - - this.currentReportedOffset = HAService.this.defaultMessageStore.getMaxPhyOffset(); - - this.lastWriteTimestamp = System.currentTimeMillis(); - } - - return this.socketChannel != null; - } - - private 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; - } catch (IOException e) { - log.warn("closeMaster exception. ", e); - } - - this.lastWriteTimestamp = 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); - } - } + /** + * Update master address + * + * @param newAddr + */ + void updateMasterAddress(String newAddr); - @Override - public void run() { - log.info(this.getServiceName() + " service started"); + /** + * Update ha master address + * + * @param newAddr + */ + void updateHaMasterAddress(String newAddr); - while (!this.isStopped()) { - try { - if (this.connectMaster()) { + /** + * 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); - if (this.isTimeToReportOffset()) { - boolean result = this.reportSlaveMaxOffset(this.currentReportedOffset); - if (!result) { - this.closeMaster(); - } - } + /** + * Get connection count + * + * @return the number of connection + */ + AtomicInteger getConnectionCount(); - this.selector.select(1000); + /** + * Put request to handle HA + * + * @param request + */ + void putRequest(final CommitLog.GroupCommitRequest request); - boolean ok = this.processReadEvent(); - if (!ok) { - this.closeMaster(); - } + /** + * Put GroupConnectionStateRequest for preOnline + * + * @param request + */ + void putGroupConnectionStateRequest(HAConnectionStateNotificationRequest request); - if (!reportSlaveMaxOffsetPlus()) { - continue; - } + /** + * Get ha connection list + * + * @return List + */ + List getConnectionList(); - long interval = - HAService.this.getDefaultMessageStore().getSystemClock().now() - - this.lastWriteTimestamp; - if (interval > HAService.this.getDefaultMessageStore().getMessageStoreConfig() - .getHaHousekeepingInterval()) { - log.warn("HAClient, housekeeping, found this connection[" + this.masterAddress - + "] expired, " + interval); - this.closeMaster(); - log.warn("HAClient, master not response some time, so close connection"); - } - } else { - this.waitForRunning(1000 * 5); - } - } catch (Exception e) { - log.warn(this.getServiceName() + " service has exception. ", e); - this.waitForRunning(1000 * 5); - } - } + /** + * Get HAClient + * + * @return HAClient + */ + HAClient getHAClient(); - log.info(this.getServiceName() + " service end"); - } + /** + * Get the max offset in all slaves + */ + AtomicLong getPush2SlaveMaxOffset(); - @Override - public void shutdown() { - super.shutdown(); - closeMaster(); - } + /** + * Get HA runtime info + */ + HARuntimeInfo getRuntimeInfo(final long masterPutWhere); - // private void disableWriteFlag() { - // if (this.socketChannel != null) { - // SelectionKey sk = this.socketChannel.keyFor(this.selector); - // if (sk != null) { - // int ops = sk.interestOps(); - // ops &= ~SelectionKey.OP_WRITE; - // sk.interestOps(ops); - // } - // } - // } - // private void enableWriteFlag() { - // if (this.socketChannel != null) { - // SelectionKey sk = this.socketChannel.keyFor(this.selector); - // if (sk != null) { - // int ops = sk.interestOps(); - // ops |= SelectionKey.OP_WRITE; - // sk.interestOps(ops); - // } - // } - // } + /** + * Get WaitNotifyObject + */ + WaitNotifyObject getWaitNotifyObject(); - @Override - public String getServiceName() { - return HAClient.class.getSimpleName(); - } - } + /** + * 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); } diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/WaitNotifyObject.java b/store/src/main/java/org/apache/rocketmq/store/ha/WaitNotifyObject.java index d5ed65f5f76..c040bf98b3a 100644 --- a/store/src/main/java/org/apache/rocketmq/store/ha/WaitNotifyObject.java +++ b/store/src/main/java/org/apache/rocketmq/store/ha/WaitNotifyObject.java @@ -17,18 +17,19 @@ package org.apache.rocketmq.store.ha; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); protected final ConcurrentHashMap waitingThreadTable = - new ConcurrentHashMap(16); + new ConcurrentHashMap<>(16); protected AtomicBoolean hasNotified = new AtomicBoolean(false); @@ -67,7 +68,7 @@ protected void onWaitEnd() { public void wakeupAll() { boolean needNotify = false; - for (Map.Entry entry : this.waitingThreadTable.entrySet()) { + for (Map.Entry entry : this.waitingThreadTable.entrySet()) { if (entry.getValue().compareAndSet(false, true)) { needNotify = true; } @@ -81,7 +82,7 @@ public void wakeupAll() { public void allWaitForRunning(long interval) { long currentThreadId = Thread.currentThread().getId(); - AtomicBoolean notified = this.waitingThreadTable.computeIfAbsent(currentThreadId, k -> new AtomicBoolean(false)); + AtomicBoolean notified = ConcurrentHashMapUtils.computeIfAbsent(this.waitingThreadTable, currentThreadId, k -> new AtomicBoolean(false)); if (notified.compareAndSet(true, false)) { this.onWaitEnd(); return; diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAClient.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAClient.java new file mode 100644 index 00000000000..936db0c4c6e --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAClient.java @@ -0,0 +1,598 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 IOException { + 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; + } + 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; + } + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAConnection.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAConnection.java new file mode 100644 index 00000000000..440cd3c7a50 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAConnection.java @@ -0,0 +1,744 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } + + // 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(); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java new file mode 100644 index 00000000000..6dc734e0c97 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java @@ -0,0 +1,575 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.ThreadFactoryImpl; +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.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 java.io.IOException; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.HashSet; +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.Executors; +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; + +/** + * 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 = Executors.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 brokerControllerId = 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()) { + final Set syncStateSet = getLocalSyncStateSet(); + Long slave = ((AutoSwitchHAConnection) conn).getSlaveId(); + if (syncStateSet.contains(slave)) { + syncStateSet.remove(slave); + markSynchronizingSyncStateSet(syncStateSet); + notifySyncStateSetChanged(syncStateSet); + } + } + super.removeConnection(conn); + } + + @Override + public boolean changeToMaster(int masterEpoch) { + 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. + for (Long slaveBrokerId : newSyncStateSet) { + if (!this.connectionCaughtUpTimeTable.containsKey(slaveBrokerId)) { + newSyncStateSet.remove(slaveBrokerId); + 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.brokerControllerId != null && !Objects.equals(syncId, this.brokerControllerId)) { + 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() { + 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 getBrokerControllerId() { + return brokerControllerId; + } + + public void setBrokerControllerId(Long brokerControllerId) { + this.brokerControllerId = brokerControllerId; + } + + 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); + } + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/BrokerMetadata.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/BrokerMetadata.java new file mode 100644 index 00000000000..eb6ab639f23 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/BrokerMetadata.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + '\'' + + '}'; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/EpochFileCache.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/EpochFileCache.java new file mode 100644 index 00000000000..f23e4aa06bf --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/EpochFileCache.java @@ -0,0 +1,327 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/MetadataFile.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/MetadataFile.java new file mode 100644 index 00000000000..e89aedbea14 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/MetadataFile.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/TempBrokerMetadata.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/TempBrokerMetadata.java new file mode 100644 index 00000000000..7a4126b02ab --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/TempBrokerMetadata.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + '\'' + + '}'; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/io/AbstractHAReader.java b/store/src/main/java/org/apache/rocketmq/store/ha/io/AbstractHAReader.java new file mode 100644 index 00000000000..b71e2160b33 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/io/AbstractHAReader.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/io/HAReadHook.java b/store/src/main/java/org/apache/rocketmq/store/ha/io/HAReadHook.java new file mode 100644 index 00000000000..4efcadd5580 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/io/HAReadHook.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/io/HAWriteHook.java b/store/src/main/java/org/apache/rocketmq/store/ha/io/HAWriteHook.java new file mode 100644 index 00000000000..9594328880a --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/io/HAWriteHook.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/io/HAWriter.java b/store/src/main/java/org/apache/rocketmq/store/ha/io/HAWriter.java new file mode 100644 index 00000000000..0f5699bac13 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/ha/io/HAWriter.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/hook/PutMessageHook.java b/store/src/main/java/org/apache/rocketmq/store/hook/PutMessageHook.java new file mode 100644 index 00000000000..dc47d32934c --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/hook/PutMessageHook.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/hook/SendMessageBackHook.java b/store/src/main/java/org/apache/rocketmq/store/hook/SendMessageBackHook.java new file mode 100644 index 00000000000..02254503217 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/hook/SendMessageBackHook.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/index/IndexFile.java b/store/src/main/java/org/apache/rocketmq/store/index/IndexFile.java index e513edf1114..9e0669fa035 100644 --- a/store/src/main/java/org/apache/rocketmq/store/index/IndexFile.java +++ b/store/src/main/java/org/apache/rocketmq/store/index/IndexFile.java @@ -19,32 +19,44 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; import java.util.List; +import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.store.MappedFile; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + 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 FileChannel fileChannel; 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 { - int fileTotalSize = + this.fileTotalSize = IndexHeader.INDEX_HEADER_SIZE + (hashSlotNum * hashSlotSize) + (indexNum * indexSize); - this.mappedFile = new MappedFile(fileName, fileTotalSize); - this.fileChannel = this.mappedFile.getFileChannel(); + this.mappedFile = new DefaultMappedFile(fileName, fileTotalSize); this.mappedByteBuffer = this.mappedFile.getMappedByteBuffer(); this.hashSlotNum = hashSlotNum; this.indexNum = indexNum; @@ -67,10 +79,19 @@ public String getFileName() { return this.mappedFile.getFileName(); } + public int getFileSize() { + return this.fileTotalSize; + } + public void load() { this.indexHeader.load(); } + public void shutdown() { + this.flush(); + UtilAll.cleanBuffer(this.mappedByteBuffer); + } + public void flush() { long beginTime = System.currentTimeMillis(); if (this.mappedFile.hold()) { @@ -95,12 +116,8 @@ public boolean putKey(final String key, final long phyOffset, final long storeTi int slotPos = keyHash % this.hashSlotNum; int absSlotPos = IndexHeader.INDEX_HEADER_SIZE + slotPos * hashSlotSize; - FileLock fileLock = null; - try { - // fileLock = this.fileChannel.lock(absSlotPos, hashSlotSize, - // false); int slotValue = this.mappedByteBuffer.getInt(absSlotPos); if (slotValue <= invalidIndex || slotValue > this.indexHeader.getIndexCount()) { slotValue = invalidIndex; @@ -144,14 +161,6 @@ public boolean putKey(final String key, final long phyOffset, final long storeTi return true; } catch (Exception e) { log.error("putKey exception, Key: " + key + " KeyHashCode: " + key.hashCode(), e); - } finally { - if (fileLock != null) { - try { - fileLock.release(); - } catch (IOException e) { - log.error("Failed to release the lock", e); - } - } } } else { log.warn("Over index file capacity: index count = " + this.indexHeader.getIndexCount() @@ -164,8 +173,9 @@ public boolean putKey(final String key, final long phyOffset, final long storeTi public int indexKeyHashMethod(final String key) { int keyHash = key.hashCode(); int keyHashPositive = Math.abs(keyHash); - if (keyHashPositive < 0) + if (keyHashPositive < 0) { keyHashPositive = 0; + } return keyHashPositive; } @@ -183,31 +193,20 @@ public long 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()); + 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, boolean lock) { + 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; - FileLock fileLock = null; try { - if (lock) { - // fileLock = this.fileChannel.lock(absSlotPos, - // hashSlotSize, true); - } - int slotValue = this.mappedByteBuffer.getInt(absSlotPos); - // if (fileLock != null) { - // fileLock.release(); - // fileLock = null; - // } - if (slotValue <= invalidIndex || slotValue > this.indexHeader.getIndexCount() || this.indexHeader.getIndexCount() <= 1) { } else { @@ -223,7 +222,7 @@ public void selectPhyOffset(final List phyOffsets, final String key, final int keyHashRead = this.mappedByteBuffer.getInt(absIndexPos); long phyOffsetRead = this.mappedByteBuffer.getLong(absIndexPos + 4); - long timeDiff = (long) this.mappedByteBuffer.getInt(absIndexPos + 4 + 8); + long timeDiff = this.mappedByteBuffer.getInt(absIndexPos + 4 + 8); int prevIndexRead = this.mappedByteBuffer.getInt(absIndexPos + 4 + 8 + 4); if (timeDiff < 0) { @@ -233,7 +232,7 @@ public void selectPhyOffset(final List phyOffsets, final String key, final timeDiff *= 1000L; long timeRead = this.indexHeader.getBeginTimestamp() + timeDiff; - boolean timeMatched = (timeRead >= begin) && (timeRead <= end); + boolean timeMatched = timeRead >= begin && timeRead <= end; if (keyHash == keyHashRead && timeMatched) { phyOffsets.add(phyOffsetRead); @@ -251,14 +250,6 @@ public void selectPhyOffset(final List phyOffsets, final String key, final } catch (Exception e) { log.error("selectPhyOffset exception ", e); } finally { - if (fileLock != null) { - try { - fileLock.release(); - } catch (IOException e) { - log.error("Failed to release the lock", e); - } - } - this.mappedFile.release(); } } diff --git a/store/src/main/java/org/apache/rocketmq/store/index/IndexHeader.java b/store/src/main/java/org/apache/rocketmq/store/index/IndexHeader.java index 44021cd5895..fe319caada9 100644 --- a/store/src/main/java/org/apache/rocketmq/store/index/IndexHeader.java +++ b/store/src/main/java/org/apache/rocketmq/store/index/IndexHeader.java @@ -20,6 +20,19 @@ 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; @@ -29,13 +42,12 @@ public class IndexHeader { private static int hashSlotcountIndex = 32; private static int indexCountIndex = 36; private final ByteBuffer byteBuffer; - private AtomicLong beginTimestamp = new AtomicLong(0); - private AtomicLong endTimestamp = new AtomicLong(0); - private AtomicLong beginPhyOffset = new AtomicLong(0); - private AtomicLong endPhyOffset = new AtomicLong(0); - private AtomicInteger hashSlotCount = new AtomicInteger(0); - - private AtomicInteger indexCount = new AtomicInteger(1); + 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; diff --git a/store/src/main/java/org/apache/rocketmq/store/index/IndexService.java b/store/src/main/java/org/apache/rocketmq/store/index/IndexService.java index bf17ecffeaf..ef5d21ac7ce 100644 --- a/store/src/main/java/org/apache/rocketmq/store/index/IndexService.java +++ b/store/src/main/java/org/apache/rocketmq/store/index/IndexService.java @@ -23,10 +23,11 @@ import java.util.List; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.apache.rocketmq.common.AbstractBrokerRunnable; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.DefaultMessageStore; @@ -34,7 +35,7 @@ import org.apache.rocketmq.store.config.StorePathConfigHelper; public class IndexService { - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); /** * Maximum times to attempt index file creation. */ @@ -43,7 +44,7 @@ public class IndexService { private final int hashSlotNum; private final int indexNum; private final String storePath; - private final ArrayList indexFileList = new ArrayList(); + private final ArrayList indexFileList = new ArrayList<>(); private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public IndexService(final DefaultMessageStore store) { @@ -51,7 +52,7 @@ public IndexService(final DefaultMessageStore store) { this.hashSlotNum = store.getMessageStoreConfig().getMaxHashSlotNum(); this.indexNum = store.getMessageStoreConfig().getMaxIndexNum(); this.storePath = - StorePathConfigHelper.getStorePathIndex(store.getMessageStoreConfig().getStorePathRootDir()); + StorePathConfigHelper.getStorePathIndex(defaultMessageStore.getMessageStoreConfig().getStorePathRootDir()); } public boolean load(final boolean lastExitOK) { @@ -73,13 +74,13 @@ public boolean load(final boolean lastExitOK) { } } - log.info("load index file OK, " + f.getFileName()); + LOGGER.info("load index file OK, " + f.getFileName()); this.indexFileList.add(f); } catch (IOException e) { - log.error("load file {} error", file, e); + LOGGER.error("load file {} error", file, e); return false; } catch (NumberFormatException e) { - log.error("load file {} error", file, e); + LOGGER.error("load file {} error", file, e); } } } @@ -87,6 +88,14 @@ public boolean load(final boolean lastExitOK) { 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 { @@ -100,13 +109,13 @@ public void deleteExpiredFile(long offset) { files = this.indexFileList.toArray(); } } catch (Exception e) { - log.error("destroy exception", e); + LOGGER.error("destroy exception", e); } finally { this.readWriteLock.readLock().unlock(); } if (files != null) { - List fileList = new ArrayList(); + List fileList = new ArrayList<>(); for (int i = 0; i < (files.length - 1); i++) { IndexFile f = (IndexFile) files[i]; if (f.getEndPhyOffset() < offset) { @@ -128,12 +137,12 @@ private void deleteExpiredFile(List files) { boolean destroyed = file.destroy(3000); destroyed = destroyed && this.indexFileList.remove(file); if (!destroyed) { - log.error("deleteExpiredFile remove failed."); + LOGGER.error("deleteExpiredFile remove failed."); break; } } } catch (Exception e) { - log.error("deleteExpiredFile has exception.", e); + LOGGER.error("deleteExpiredFile has exception.", e); } finally { this.readWriteLock.writeLock().unlock(); } @@ -148,14 +157,14 @@ public void destroy() { } this.indexFileList.clear(); } catch (Exception e) { - log.error("destroy 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) { - List phyOffsets = new ArrayList(maxNum); + List phyOffsets = new ArrayList<>(maxNum); long indexLastUpdateTimestamp = 0; long indexLastUpdatePhyoffset = 0; @@ -173,7 +182,7 @@ public QueryOffsetResult queryOffset(String topic, String key, int maxNum, long if (f.isTimeMatched(begin, end)) { - f.selectPhyOffset(phyOffsets, buildKey(topic, key), maxNum, begin, end, lastFile); + f.selectPhyOffset(phyOffsets, buildKey(topic, key), maxNum, begin, end); } if (f.getBeginTimestamp() < begin) { @@ -186,7 +195,7 @@ public QueryOffsetResult queryOffset(String topic, String key, int maxNum, long } } } catch (Exception e) { - log.error("queryMsg exception", e); + LOGGER.error("queryMsg exception", e); } finally { this.readWriteLock.readLock().unlock(); } @@ -222,7 +231,7 @@ public void buildIndex(DispatchRequest req) { if (req.getUniqKey() != null) { indexFile = putKey(indexFile, msg, buildKey(topic, req.getUniqKey())); if (indexFile == null) { - log.error("putKey error commitlog {} uniqkey {}", req.getCommitLogOffset(), req.getUniqKey()); + LOGGER.error("putKey error commitlog {} uniqkey {}", req.getCommitLogOffset(), req.getUniqKey()); return; } } @@ -234,20 +243,20 @@ public void buildIndex(DispatchRequest req) { if (key.length() > 0) { indexFile = putKey(indexFile, msg, buildKey(topic, key)); if (indexFile == null) { - log.error("putKey error commitlog {} uniqkey {}", req.getCommitLogOffset(), req.getUniqKey()); + LOGGER.error("putKey error commitlog {} uniqkey {}", req.getCommitLogOffset(), req.getUniqKey()); return; } } } } } else { - log.error("build index error, stop building index"); + 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; ) { - log.warn("Index file [" + indexFile.getFileName() + "] is full, trying to create another one"); + LOGGER.warn("Index file [" + indexFile.getFileName() + "] is full, trying to create another one"); indexFile = retryGetAndCreateIndexFile(); if (null == indexFile) { @@ -270,20 +279,21 @@ public IndexFile retryGetAndCreateIndexFile() { for (int times = 0; null == indexFile && times < MAX_TRY_IDX_CREATE; times++) { indexFile = this.getAndCreateLastIndexFile(); - if (null != indexFile) + if (null != indexFile) { break; + } try { - log.info("Tried to create index file " + times + " times"); + LOGGER.info("Tried to create index file " + times + " times"); Thread.sleep(1000); } catch (InterruptedException e) { - log.error("Interrupted", e); + LOGGER.error("Interrupted", e); } } if (null == indexFile) { - this.defaultMessageStore.getAccessRights().makeIndexFileError(); - log.error("Mark index file cannot build flag"); + this.defaultMessageStore.getRunningFlags().makeIndexFileError(); + LOGGER.error("Mark index file cannot build flag"); } return indexFile; @@ -322,16 +332,17 @@ public IndexFile getAndCreateLastIndexFile() { this.readWriteLock.writeLock().lock(); this.indexFileList.add(indexFile); } catch (Exception e) { - log.error("getLastIndexFile 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() { + + Thread flushThread = new Thread(new AbstractBrokerRunnable(defaultMessageStore.getBrokerConfig()) { @Override - public void run() { + public void run0() { IndexService.this.flush(flushThisFile); } }, "FlushIndexFileThread"); @@ -345,8 +356,9 @@ public void run() { } public void flush(final IndexFile f) { - if (null == f) + if (null == f) { return; + } long indexMsgTimestamp = 0; @@ -367,6 +379,20 @@ 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(); + } } } diff --git a/store/src/main/java/org/apache/rocketmq/store/kv/CommitLogDispatcherCompaction.java b/store/src/main/java/org/apache/rocketmq/store/kv/CommitLogDispatcherCompaction.java new file mode 100644 index 00000000000..5c285b144a9 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/kv/CommitLogDispatcherCompaction.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/kv/CompactionLog.java b/store/src/main/java/org/apache/rocketmq/store/kv/CompactionLog.java new file mode 100644 index 00000000000..be2bb551ad7 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/kv/CompactionLog.java @@ -0,0 +1,1149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + } + +} diff --git a/store/src/main/java/org/apache/rocketmq/store/kv/CompactionPositionMgr.java b/store/src/main/java/org/apache/rocketmq/store/kv/CompactionPositionMgr.java new file mode 100644 index 00000000000..4181b34b8b4 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/kv/CompactionPositionMgr.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/kv/CompactionService.java b/store/src/main/java/org/apache/rocketmq/store/kv/CompactionService.java new file mode 100644 index 00000000000..5e07a50082b --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/kv/CompactionService.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/kv/CompactionStore.java b/store/src/main/java/org/apache/rocketmq/store/kv/CompactionStore.java new file mode 100644 index 00000000000..b37c907267c --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/kv/CompactionStore.java @@ -0,0 +1,247 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.Random; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +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.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; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +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 = Executors.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; + } + +} diff --git a/store/src/main/java/org/apache/rocketmq/store/kv/MessageFetcher.java b/store/src/main/java/org/apache/rocketmq/store/kv/MessageFetcher.java new file mode 100644 index 00000000000..0ce0a3d8da2 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/kv/MessageFetcher.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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, null); + 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)); + } + } + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/logfile/AbstractMappedFile.java b/store/src/main/java/org/apache/rocketmq/store/logfile/AbstractMappedFile.java new file mode 100644 index 00000000000..28d443cddec --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/logfile/AbstractMappedFile.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 { +} diff --git a/store/src/main/java/org/apache/rocketmq/store/logfile/DefaultMappedFile.java b/store/src/main/java/org/apache/rocketmq/store/logfile/DefaultMappedFile.java new file mode 100644 index 00000000000..03477c33249 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/logfile/DefaultMappedFile.java @@ -0,0 +1,929 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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.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; +import sun.nio.ch.DirectBuffer; + +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; + 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; + + 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 { + init(fileName, fileSize); + } + + public DefaultMappedFile(final String fileName, final int fileSize, + final TransientStorePool transientStorePool) throws IOException { + init(fileName, fileSize, 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 TransientStorePool transientStorePool) throws IOException { + init(fileName, fileSize); + this.writeBuffer = transientStorePool.borrowBuffer(); + this.transientStorePool = transientStorePool; + } + + private void init(final String fileName, final int fileSize) throws IOException { + this.fileName = fileName; + this.fileSize = fileSize; + this.file = new File(fileName); + this.fileFromOffset = Long.parseLong(this.file.getName()); + boolean ok = false; + + UtilAll.ensureDirOK(this.file.getParent()); + + try { + this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel(); + 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); + if (currentPos < this.fileSize) { + ByteBuffer byteBuffer = appendMessageBuffer().slice(); + byteBuffer.position(currentPos); + AppendMessageResult result = cb.doAppend(byteBuffer, this.fileFromOffset, this.fileSize - currentPos, byteBufferMsg); + 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); + } + + @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); + + if (currentPos < this.fileSize) { + ByteBuffer byteBuffer = appendMessageBuffer().slice(); + byteBuffer.position(currentPos); + AppendMessageResult result; + if (messageExt instanceof MessageExtBatch && !((MessageExtBatch) messageExt).isInnerBatch()) { + // traditional batch message + result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, + (MessageExtBatch) messageExt, putMessageContext); + } else if (messageExt instanceof MessageExtBrokerInner) { + // traditional single message or newly introduced inner-batch message + result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, + (MessageExtBrokerInner) messageExt, putMessageContext); + } else { + return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); + } + 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 { + this.fileChannel.position(currentPos); + while (data.hasRemaining()) { + this.fileChannel.write(data); + } + } catch (Throwable e) { + log.error("Error occurred when append message to mappedFile.", e); + } + WROTE_POSITION_UPDATER.addAndGet(this, remaining); + return true; + } + 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 { + ByteBuffer buf = this.mappedByteBuffer.slice(); + buf.position(currentPos); + buf.put(data, offset, length); + } catch (Throwable e) { + log.error("Error occurred when append message to mappedFile.", e); + } + WROTE_POSITION_UPDATER.addAndGet(this, length); + return true; + } + + return false; + } + + /** + * @return The current flushed position + */ + @Override + public int flush(final int flushLeastPages) { + if (this.isAbleToFlush(flushLeastPages)) { + if (this.hold()) { + int value = getReadPosition(); + + try { + this.mappedByteBufferAccessCountSinceLastSwap++; + + //We only append data to fileChannel or mappedByteBuffer, never both. + if (writeBuffer != null || this.fileChannel.position() != 0) { + this.fileChannel.force(false); + } else { + this.mappedByteBuffer.force(); + } + this.lastFlushTime = System.currentTimeMillis(); + } catch (Throwable e) { + log.error("Error occurred when force data to disk.", e); + } + + FLUSHED_POSITION_UPDATER.set(this, value); + 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); + } + } + } + + 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; + } + + UtilAll.cleanBuffer(this.mappedByteBuffer); + UtilAll.cleanBuffer(this.mappedByteBufferWaitToClean); + this.mappedByteBufferWaitToClean = null; + TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(this.fileSize * (-1)); + TOTAL_MAPPED_FILES.decrementAndGet(); + log.info("unmap file[REF:" + currentRef + "] " + this.fileName + " OK"); + return true; + } + + @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 = ((DirectBuffer) (this.mappedByteBuffer)).address(); + 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 = ((DirectBuffer) (this.mappedByteBuffer)).address(); + 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 = ((DirectBuffer) mappedByteBuffer).address() + 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(); + } + } + +} diff --git a/store/src/main/java/org/apache/rocketmq/store/logfile/MappedFile.java b/store/src/main/java/org/apache/rocketmq/store/logfile/MappedFile.java new file mode 100644 index 00000000000..dfcf66f0882 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/logfile/MappedFile.java @@ -0,0 +1,371 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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}. + * + * @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}. + * + * @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 If {@code true} then this method will destroy the file forcibly and ignore the reference + * @return true if success; false otherwise. + */ + boolean destroy(long intervalForcibly); + + /** + * Shutdowns the file and mark it unavailable. + * + * @param intervalForcibly If {@code true} then this method will shutdown the file forcibly and ignore the reference + */ + 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); + + /** + * 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, 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); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/metrics/DefaultStoreMetricsConstant.java b/store/src/main/java/org/apache/rocketmq/store/metrics/DefaultStoreMetricsConstant.java new file mode 100644 index 00000000000..271604b1e50 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/metrics/DefaultStoreMetricsConstant.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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"; +} diff --git a/store/src/main/java/org/apache/rocketmq/store/metrics/DefaultStoreMetricsManager.java b/store/src/main/java/org/apache/rocketmq/store/metrics/DefaultStoreMetricsManager.java new file mode 100644 index 00000000000..ff87f6369f8 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/metrics/DefaultStoreMetricsManager.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Meter; +import io.opentelemetry.api.metrics.ObservableLongGauge; +import io.opentelemetry.sdk.metrics.InstrumentSelector; +import io.opentelemetry.sdk.metrics.View; +import java.io.File; +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.NopObservableLongGauge; +import org.apache.rocketmq.store.DefaultMessageStore; +import org.apache.rocketmq.store.config.MessageStoreConfig; +import org.apache.rocketmq.store.timer.TimerMessageStore; + +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_TIMING_MESSAGES; +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_TOPIC; + +public class DefaultStoreMetricsManager { + public static Supplier attributesBuilderSupplier; + public static MessageStoreConfig messageStoreConfig; + + public static ObservableLongGauge storageSize = new NopObservableLongGauge(); + public static ObservableLongGauge flushBehind = new NopObservableLongGauge(); + public static ObservableLongGauge dispatchBehind = new NopObservableLongGauge(); + public static ObservableLongGauge messageReserveTime = new NopObservableLongGauge(); + + public static ObservableLongGauge timerEnqueueLag = new NopObservableLongGauge(); + public static ObservableLongGauge timerEnqueueLatency = new NopObservableLongGauge(); + public static ObservableLongGauge timerDequeueLag = new NopObservableLongGauge(); + public static ObservableLongGauge timerDequeueLatency = new NopObservableLongGauge(); + public static ObservableLongGauge timingMessages = new NopObservableLongGauge(); + + public static LongCounter timerDequeueTotal = new NopLongCounter(); + public static LongCounter timerEnqueueTotal = new NopLongCounter(); + + public static List> getMetricsView() { + return Lists.newArrayList(); + } + + public static void init(Meter meter, Supplier attributesBuilderSupplier, + DefaultMessageStore messageStore) { + DefaultStoreMetricsManager.attributesBuilderSupplier = attributesBuilderSupplier; + DefaultStoreMetricsManager.messageStoreConfig = messageStore.getMessageStoreConfig(); + + storageSize = meter.gaugeBuilder(GAUGE_STORAGE_SIZE) + .setDescription("Broker storage size") + .setUnit("bytes") + .ofLongs() + .buildWithCallback(measurement -> { + File storeDir = new File(messageStoreConfig.getStorePathRootDir()); + if (storeDir.exists() && storeDir.isDirectory()) { + long totalSpace = storeDir.getTotalSpace(); + if (totalSpace > 0) { + measurement.record(totalSpace - storeDir.getFreeSpace(), newAttributesBuilder().build()); + } + } + }); + + flushBehind = meter.gaugeBuilder(GAUGE_STORAGE_FLUSH_BEHIND) + .setDescription("Broker flush behind bytes") + .setUnit("bytes") + .ofLongs() + .buildWithCallback(measurement -> measurement.record(messageStore.flushBehindBytes(), newAttributesBuilder().build())); + + dispatchBehind = meter.gaugeBuilder(GAUGE_STORAGE_DISPATCH_BEHIND) + .setDescription("Broker dispatch behind bytes") + .setUnit("bytes") + .ofLongs() + .buildWithCallback(measurement -> measurement.record(messageStore.dispatchBehindBytes(), newAttributesBuilder().build())); + + 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, newAttributesBuilder().build()); + }); + + if (messageStore.getMessageStoreConfig().isTimerWheelEnable()) { + timerEnqueueLag = meter.gaugeBuilder(GAUGE_TIMER_ENQUEUE_LAG) + .setDescription("Timer enqueue messages lag") + .ofLongs() + .buildWithCallback(measurement -> { + TimerMessageStore timerMessageStore = messageStore.getTimerMessageStore(); + measurement.record(timerMessageStore.getEnqueueBehindMessages(), newAttributesBuilder().build()); + }); + + timerEnqueueLatency = meter.gaugeBuilder(GAUGE_TIMER_ENQUEUE_LATENCY) + .setDescription("Timer enqueue latency") + .setUnit("milliseconds") + .ofLongs() + .buildWithCallback(measurement -> { + TimerMessageStore timerMessageStore = messageStore.getTimerMessageStore(); + measurement.record(timerMessageStore.getEnqueueBehindMillis(), newAttributesBuilder().build()); + }); + timerDequeueLag = meter.gaugeBuilder(GAUGE_TIMER_DEQUEUE_LAG) + .setDescription("Timer dequeue messages lag") + .ofLongs() + .buildWithCallback(measurement -> { + TimerMessageStore timerMessageStore = messageStore.getTimerMessageStore(); + measurement.record(timerMessageStore.getDequeueBehindMessages(), newAttributesBuilder().build()); + }); + timerDequeueLatency = meter.gaugeBuilder(GAUGE_TIMER_DEQUEUE_LATENCY) + .setDescription("Timer dequeue latency") + .setUnit("milliseconds") + .ofLongs() + .buildWithCallback(measurement -> { + TimerMessageStore timerMessageStore = messageStore.getTimerMessageStore(); + measurement.record(timerMessageStore.getDequeueBehind(), newAttributesBuilder().build()); + }); + 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(), + newAttributesBuilder().put(LABEL_TOPIC, topic).build() + ); + }); + }); + timerDequeueTotal = meter.counterBuilder(COUNTER_TIMER_DEQUEUE_TOTAL) + .setDescription("Total number of timer dequeue") + .build(); + timerEnqueueTotal = meter.counterBuilder(COUNTER_TIMER_ENQUEUE_TOTAL) + .setDescription("Total number of timer enqueue") + .build(); + } + } + + public static void incTimerDequeueCount(String topic) { + timerDequeueTotal.add(1, newAttributesBuilder() + .put(LABEL_TOPIC, topic) + .build()); + } + + public static void incTimerEnqueueCount(String topic) { + AttributesBuilder attributesBuilder = newAttributesBuilder(); + if (topic != null) { + attributesBuilder.put(LABEL_TOPIC, topic); + } + timerEnqueueTotal.add(1, attributesBuilder.build()); + } + + public static AttributesBuilder newAttributesBuilder() { + if (attributesBuilderSupplier == null) { + return Attributes.builder(); + } + return attributesBuilderSupplier.get() + .put(LABEL_STORAGE_TYPE, DEFAULT_STORAGE_TYPE) + .put(LABEL_STORAGE_MEDIUM, DEFAULT_STORAGE_MEDIUM); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/plugin/AbstractPluginMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/plugin/AbstractPluginMessageStore.java new file mode 100644 index 00000000000..25e947512ff --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/plugin/AbstractPluginMessageStore.java @@ -0,0 +1,654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.View; + +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.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.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.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.ConsumeQueueStore; +import org.apache.rocketmq.store.stats.BrokerStatsManager; +import org.apache.rocketmq.store.timer.TimerMessageStore; +import org.apache.rocketmq.store.util.PerfCounter; + +public abstract class AbstractPluginMessageStore implements MessageStore { + protected MessageStore next = null; + 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) { + return next.getMaxOffsetInQueue(topic, queueId); + } + + @Override + public long getMaxOffsetInQueue(String topic, int queueId, boolean committed) { + 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 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 flush() { + return next.flush(); + } + + @Override + public boolean resetWriteOffset(long phyOffset) { + return next.resetWriteOffset(phyOffset); + } + + @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) { + 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) { + 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) { + next.truncateDirtyLogicFiles(phyOffset); + } + + @Override + public void destroyLogics() { + next.destroyLogics(); + } + + @Override + public void unlockMappedFile(MappedFile unlockMappedFile) { + next.unlockMappedFile(unlockMappedFile); + } + + @Override + public PerfCounter.Ticks getPerfCounter() { + return next.getPerfCounter(); + } + + @Override + public ConsumeQueueStore getQueueStore() { + return next.getQueueStore(); + } + + @Override + public boolean isSyncDiskFlush() { + return next.isSyncDiskFlush(); + } + + @Override + public boolean isSyncMaster() { + return next.isSyncMaster(); + } + + @Override + public void assignOffset(MessageExtBrokerInner msg) { + 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); + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/plugin/MessageStoreFactory.java b/store/src/main/java/org/apache/rocketmq/store/plugin/MessageStoreFactory.java similarity index 79% rename from broker/src/main/java/org/apache/rocketmq/broker/plugin/MessageStoreFactory.java rename to store/src/main/java/org/apache/rocketmq/store/plugin/MessageStoreFactory.java index 8db538b84df..8d929ea5651 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/plugin/MessageStoreFactory.java +++ b/store/src/main/java/org/apache/rocketmq/store/plugin/MessageStoreFactory.java @@ -1,45 +1,45 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.io.IOException; -import java.lang.reflect.Constructor; -import org.apache.rocketmq.store.MessageStore; - -public final class MessageStoreFactory { - public final 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); - messageStore = construct.newInstance(context, messageStore); - } catch (Throwable e) { - throw new RuntimeException(String.format( - "Initialize plugin's class %s not found!", pluginClass), e); - } - } - } - return messageStore; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/plugin/MessageStorePluginContext.java b/store/src/main/java/org/apache/rocketmq/store/plugin/MessageStorePluginContext.java similarity index 81% rename from broker/src/main/java/org/apache/rocketmq/broker/plugin/MessageStorePluginContext.java rename to store/src/main/java/org/apache/rocketmq/store/plugin/MessageStorePluginContext.java index b822a2f7585..d39ccddf8aa 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/plugin/MessageStorePluginContext.java +++ b/store/src/main/java/org/apache/rocketmq/store/plugin/MessageStorePluginContext.java @@ -1,57 +1,65 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 org.apache.rocketmq.common.BrokerConfig; -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; - - public MessageStorePluginContext(MessageStoreConfig messageStoreConfig, - BrokerStatsManager brokerStatsManager, MessageArrivingListener messageArrivingListener, - BrokerConfig brokerConfig) { - super(); - this.messageStoreConfig = messageStoreConfig; - this.brokerStatsManager = brokerStatsManager; - this.messageArrivingListener = messageArrivingListener; - this.brokerConfig = brokerConfig; - } - - public MessageStoreConfig getMessageStoreConfig() { - return messageStoreConfig; - } - - public BrokerStatsManager getBrokerStatsManager() { - return brokerStatsManager; - } - - public MessageArrivingListener getMessageArrivingListener() { - return messageArrivingListener; - } - - public BrokerConfig getBrokerConfig() { - return brokerConfig; - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/pop/AckMsg.java b/store/src/main/java/org/apache/rocketmq/store/pop/AckMsg.java new file mode 100644 index 00000000000..3e65c104b12 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/pop/AckMsg.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.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(); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/pop/BatchAckMsg.java b/store/src/main/java/org/apache/rocketmq/store/pop/BatchAckMsg.java new file mode 100644 index 00000000000..991a1f085de --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/pop/BatchAckMsg.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.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(); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java new file mode 100644 index 00000000000..e041b66d9c5 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.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; + @JSONField(name = "c") + private String cid; + @JSONField(name = "ro") + private long reviveOffset; + @JSONField(name = "d") + private List queueOffsetDiff; + @JSONField(name = "bn") + String brokerName; + + 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; + } + + public String getCId() { + return cid; + } + + 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 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); + } + + @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 + "]"; + } + + @Override + public int compareTo(PopCheckPoint o) { + return (int) (this.getStartOffset() - o.getStartOffset()); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/BatchConsumeQueue.java b/store/src/main/java/org/apache/rocketmq/store/queue/BatchConsumeQueue.java new file mode 100644 index 00000000000..8fec1bf7b01 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/queue/BatchConsumeQueue.java @@ -0,0 +1,1132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 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 String subfolder) { + this.storePath = storePath; + this.mappedFileSize = mappedFileSize; + this.messageStore = messageStore; + this.commitLogSize = messageStore.getCommitLog().getCommitLogSize(); + + this.topic = topic; + this.queueId = queueId; + + if (StringUtils.isBlank(subfolder)) { + String queueDir = this.storePath + File.separator + topic + File.separator + queueId; + this.mappedFileQueue = new MappedFileQueue(queueDir, mappedFileSize, null); + } else { + String queueDir = this.storePath + File.separator + topic + File.separator + queueId + File.separator + subfolder; + this.mappedFileQueue = new MappedFileQueue(queueDir, mappedFileSize, null); + } + + this.byteBufferItem = ByteBuffer.allocate(CQ_STORE_UNIT_SIZE); + } + + 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 CqUnit get(long offset) { + ReferredIterator it = iterateFrom(offset); + if (it == null) { + return null; + } + return it.nextAndRelease(); + } + + @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().setLogicsMsgTimestamp(request.getStoreTimestamp()); + 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 = 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 + */ + @Override + public long getOffsetInQueueByTime(final long timestamp) { + 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); + 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 find the earliest one. + */ + public static int binarySearchRight(ByteBuffer byteBuffer, int left, int right, final int unitSize, + final int unitShift, + long targetValue) { + 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 + if (tmpValue < targetValue) { + left = mid + unitSize; + } else { + right = mid; + } + } + } + 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; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/BatchOffsetIndex.java b/store/src/main/java/org/apache/rocketmq/store/queue/BatchOffsetIndex.java new file mode 100644 index 00000000000..8ca85c6b6f7 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/queue/BatchOffsetIndex.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueInterface.java b/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueInterface.java new file mode 100644 index 00000000000..d7213fa37a1 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueInterface.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.attribute.CQType; +import org.apache.rocketmq.common.message.MessageExtBrokerInner; +import org.apache.rocketmq.store.DispatchRequest; +import org.apache.rocketmq.store.MessageFilter; + +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 cq unit at specified index + * @param index index + * @return the cq unit at index + */ + CqUnit get(long index); + + /** + * 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); + + /** + * 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 + */ + void assignQueueOffset(QueueOffsetOperator queueOffsetAssigner, MessageExtBrokerInner msg); + + + /** + * 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); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStore.java b/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStore.java new file mode 100644 index 00000000000..8d38503b371 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStore.java @@ -0,0 +1,546 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import org.apache.rocketmq.common.ThreadFactoryImpl; +import org.apache.rocketmq.common.TopicConfig; +import org.apache.rocketmq.common.attribute.CQType; +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.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.CommitLog; +import org.apache.rocketmq.store.ConsumeQueue; +import org.apache.rocketmq.store.DefaultMessageStore; +import org.apache.rocketmq.store.DispatchRequest; +import org.apache.rocketmq.common.message.MessageExtBrokerInner; +import org.apache.rocketmq.store.SelectMappedBufferResult; +import org.apache.rocketmq.store.config.MessageStoreConfig; + +import java.io.File; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +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 { + private 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 ConsumeQueueStore(DefaultMessageStore messageStore, MessageStoreConfig messageStoreConfig) { + this.messageStore = messageStore; + this.messageStoreConfig = messageStoreConfig; + this.consumeQueueTable = new ConcurrentHashMap<>(32); + } + + private FileQueueLifeCycle getLifeCycle(String topic, int queueId) { + return findOrCreateConsumeQueue(topic, queueId); + } + + public long rollNextFile(ConsumeQueueInterface consumeQueue, final long offset) { + FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); + return fileQueueLifeCycle.rollNextFile(offset); + } + + public void correctMinOffset(ConsumeQueueInterface consumeQueue, long minCommitLogOffset) { + consumeQueue.correctMinOffset(minCommitLogOffset); + } + + /** + * Apply the dispatched request and build the consume queue. This function should be idempotent. + * + * @param consumeQueue consume queue + * @param request dispatch request + */ + public void putMessagePositionInfoWrapper(ConsumeQueueInterface consumeQueue, DispatchRequest request) { + consumeQueue.putMessagePositionInfoWrapper(request); + } + + public void putMessagePositionInfoWrapper(DispatchRequest dispatchRequest) { + ConsumeQueueInterface cq = this.findOrCreateConsumeQueue(dispatchRequest.getTopic(), dispatchRequest.getQueueId()); + this.putMessagePositionInfoWrapper(cq, dispatchRequest); + } + + public boolean load(ConsumeQueueInterface consumeQueue) { + FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); + return fileQueueLifeCycle.load(); + } + + public boolean load() { + boolean cqLoadResult = loadConsumeQueues(getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()), CQType.SimpleCQ); + boolean bcqLoadResult = loadConsumeQueues(getStorePathBatchConsumeQueue(this.messageStoreConfig.getStorePathRootDir()), CQType.BatchCQ); + return cqLoadResult && bcqLoadResult; + } + + 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); + } 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 new ThreadPoolExecutor( + 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(); + } + + public void recover() { + for (ConcurrentMap maps : this.consumeQueueTable.values()) { + for (ConsumeQueueInterface logic : maps.values()) { + this.recover(logic); + } + } + } + + 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; + } + + public long getMaxOffsetInConsumeQueue() { + long maxPhysicOffset = -1L; + for (ConcurrentMap maps : this.consumeQueueTable.values()) { + for (ConsumeQueueInterface logic : maps.values()) { + if (logic.getMaxPhysicOffset() > maxPhysicOffset) { + maxPhysicOffset = logic.getMaxPhysicOffset(); + } + } + } + return maxPhysicOffset; + } + + public void checkSelf(ConsumeQueueInterface consumeQueue) { + FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); + fileQueueLifeCycle.checkSelf(); + } + + 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 destroy(ConsumeQueueInterface consumeQueue) { + FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId()); + fileQueueLifeCycle.destroy(); + } + + 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(); + } + + public ConsumeQueueInterface findOrCreateConsumeQueue(String topic, int queueId) { + return doFindOrCreateConsumeQueue(topic, queueId); + } + + private ConsumeQueueInterface doFindOrCreateConsumeQueue(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); + } + + ConsumeQueueInterface oldLogic = map.putIfAbsent(queueId, newLogic); + if (oldLogic != null) { + logic = oldLogic; + } else { + logic = newLogic; + } + + return logic; + } + + public Long getMaxOffset(String topic, int queueId) { + return this.queueOffsetOperator.currentQueueOffset(topic + "-" + queueId); + } + + public void setTopicQueueTable(ConcurrentMap topicQueueTable) { + this.queueOffsetOperator.setTopicQueueTable(topicQueueTable); + this.queueOffsetOperator.setLmqTopicQueueTable(topicQueueTable); + } + + public ConcurrentMap getTopicQueueTable() { + return this.queueOffsetOperator.getTopicQueueTable(); + } + + public void setBatchTopicQueueTable(ConcurrentMap batchTopicQueueTable) { + this.queueOffsetOperator.setBatchTopicQueueTable(batchTopicQueueTable); + } + + public void assignQueueOffset(MessageExtBrokerInner msg) { + ConsumeQueueInterface consumeQueue = findOrCreateConsumeQueue(msg.getTopic(), msg.getQueueId()); + consumeQueue.assignQueueOffset(this.queueOffsetOperator, msg); + } + + public void increaseQueueOffset(MessageExtBrokerInner msg, short messageNum) { + ConsumeQueueInterface consumeQueue = findOrCreateConsumeQueue(msg.getTopic(), msg.getQueueId()); + consumeQueue.increaseQueueOffset(this.queueOffsetOperator, msg, messageNum); + } + + public void updateQueueOffset(String topic, int queueId, long offset) { + String topicQueueKey = topic + "-" + queueId; + this.queueOffsetOperator.updateQueueOffset(topicQueueKey, offset); + } + + public void removeTopicQueueTable(String topic, Integer queueId) { + this.queueOffsetOperator.remove(topic, queueId); + } + + public ConcurrentMap> getConsumeQueueTable() { + return consumeQueueTable; + } + + 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); + } else { + map.put(queueId, consumeQueue); + } + } + + 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()) { + SelectMappedBufferResult lastBuffer = null; + long startReadOffset = messageStore.getCommitLog().getConfirmOffset() == -1 ? 0 : messageStore.getCommitLog().getConfirmOffset(); + 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, true, 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(); + } finally { + if (lastBuffer != null) + lastBuffer.release(); + } + + } + } + + this.setTopicQueueTable(cqOffsetTable); + this.setBatchTopicQueueTable(bcqOffsetTable); + } + + public void destroy() { + for (ConcurrentMap maps : this.consumeQueueTable.values()) { + for (ConsumeQueueInterface logic : maps.values()) { + this.destroy(logic); + } + } + } + + 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(); + } + } + } + } + + public void truncateDirty(long phyOffset) { + for (ConcurrentMap maps : this.consumeQueueTable.values()) { + for (ConsumeQueueInterface logic : maps.values()) { + this.truncateDirtyLogicFiles(logic, phyOffset); + } + } + } + + public long getTotalSize() { + long totalSize = 0; + for (ConcurrentMap maps : this.consumeQueueTable.values()) { + for (ConsumeQueueInterface logic : maps.values()) { + totalSize += logic.getTotalSize(); + } + } + return totalSize; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/CqUnit.java b/store/src/main/java/org/apache/rocketmq/store/queue/CqUnit.java new file mode 100644 index 00000000000..b8865fd9195 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/queue/CqUnit.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + + ", compactedOffset=" + compactedOffset + + '}'; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/FileQueueLifeCycle.java b/store/src/main/java/org/apache/rocketmq/store/queue/FileQueueLifeCycle.java new file mode 100644 index 00000000000..95cc0887f42 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/queue/FileQueueLifeCycle.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/QueueOffsetOperator.java b/store/src/main/java/org/apache/rocketmq/store/queue/QueueOffsetOperator.java new file mode 100644 index 00000000000..2545bbf523d --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/queue/QueueOffsetOperator.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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; + +/** + * 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); + private ConcurrentMap lmqTopicQueueTable = new ConcurrentHashMap<>(1024); + + public long getQueueOffset(String topicQueueKey) { + return ConcurrentHashMapUtils.computeIfAbsent(this.topicQueueTable, topicQueueKey, k -> 0L); + } + + 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 topicQueueKey) { + return ConcurrentHashMapUtils.computeIfAbsent(this.lmqTopicQueueTable, topicQueueKey, k -> 0L); + } + + public void increaseLmqOffset(String topicQueueKey, short messageNum) { + Long lmqOffset = ConcurrentHashMapUtils.computeIfAbsent(this.lmqTopicQueueTable, topicQueueKey, k -> 0L); + this.lmqTopicQueueTable.put(topicQueueKey, lmqOffset + messageNum); + } + + 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; + } +} \ No newline at end of file diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/ReferredIterator.java b/store/src/main/java/org/apache/rocketmq/store/queue/ReferredIterator.java new file mode 100644 index 00000000000..eba8738af3b --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/queue/ReferredIterator.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); +} diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/SparseConsumeQueue.java b/store/src/main/java/org/apache/rocketmq/store/queue/SparseConsumeQueue.java new file mode 100644 index 00000000000..5b397d696bc --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/queue/SparseConsumeQueue.java @@ -0,0 +1,396 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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); + 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 = 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(); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStats.java b/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStats.java index a7cbdd36aa5..fb717550f40 100644 --- a/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStats.java +++ b/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStats.java @@ -17,14 +17,14 @@ package org.apache.rocketmq.store.stats; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.store.DefaultMessageStore; +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 InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + private static final Logger log = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); - private final DefaultMessageStore defaultMessageStore; + private final MessageStore defaultMessageStore; private volatile long msgPutTotalYesterdayMorning; @@ -34,7 +34,7 @@ public class BrokerStats { private volatile long msgGetTotalTodayMorning; - public BrokerStats(DefaultMessageStore defaultMessageStore) { + public BrokerStats(MessageStore defaultMessageStore) { this.defaultMessageStore = defaultMessageStore; } @@ -43,9 +43,9 @@ public void record() { this.msgGetTotalYesterdayMorning = this.msgGetTotalTodayMorning; this.msgPutTotalTodayMorning = - this.defaultMessageStore.getStoreStatsService().getPutMessageTimesTotal(); + this.defaultMessageStore.getBrokerStatsManager().getBrokerPutNumsWithoutSystemTopic(); this.msgGetTotalTodayMorning = - this.defaultMessageStore.getStoreStatsService().getGetMessageTransferedMsgCount().longValue(); + this.defaultMessageStore.getBrokerStatsManager().getBrokerGetNumsWithoutSystemTopic(); log.info("yesterday put message total: {}", msgPutTotalTodayMorning - msgPutTotalYesterdayMorning); log.info("yesterday get message total: {}", msgGetTotalTodayMorning - msgGetTotalYesterdayMorning); @@ -84,10 +84,10 @@ public void setMsgGetTotalTodayMorning(long msgGetTotalTodayMorning) { } public long getMsgPutTotalTodayNow() { - return this.defaultMessageStore.getStoreStatsService().getPutMessageTimesTotal(); + return this.defaultMessageStore.getBrokerStatsManager().getBrokerPutNumsWithoutSystemTopic(); } public long getMsgGetTotalTodayNow() { - return this.defaultMessageStore.getStoreStatsService().getGetMessageTransferedMsgCount().longValue(); + return this.defaultMessageStore.getBrokerStatsManager().getBrokerGetNumsWithoutSystemTopic(); } } diff --git a/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java b/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java index 531d3fd3947..2dd3fc5b52a 100644 --- a/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java +++ b/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java @@ -19,93 +19,273 @@ import java.util.HashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +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.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +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.Stats; +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.common.stats.MomentStatsItemSet; import org.apache.rocketmq.common.stats.StatsItem; import org.apache.rocketmq.common.stats.StatsItemSet; public class BrokerStatsManager { - 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"; + @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 - 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"; + @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 + 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"; + 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"; - // Message Size limit for one api-calling count. - public static final double SIZE_PER_COUNT = 64 * 1024; - 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 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 - public static final String GROUP_GET_LATENCY = "GROUP_GET_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"; /** * read disk follow stats */ - private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.ROCKETMQ_STATS_LOGGER_NAME); - private static final InternalLogger COMMERCIAL_LOG = InternalLoggerFactory.getLogger(LoggerName.COMMERCIAL_LOGGER_NAME); - private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl( - "BrokerStatsThread")); - private final ScheduledExecutorService commercialExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl( - "CommercialStatsThread")); - private final HashMap statsTable = new HashMap(); + 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 final HashMap statsTable = new HashMap<>(); private final String clusterName; private final boolean enableQueueStat; - private final MomentStatsItemSet momentStatsItemSetFallSize = new MomentStatsItemSet(GROUP_GET_FALL_SIZE, scheduledExecutorService, log); - private final MomentStatsItemSet momentStatsItemSetFallTime = new MomentStatsItemSet(GROUP_GET_FALL_TIME, scheduledExecutorService, log); + private MomentStatsItemSet momentStatsItemSetFallSize; + private MomentStatsItemSet momentStatsItemSetFallTime; + + private final StatisticsManager accountStatManager = new StatisticsManager(); + private StateGetter produerStateGetter; + 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(QUEUE_PUT_NUMS, new StatsItemSet(QUEUE_PUT_NUMS, this.scheduledExecutorService, log)); - this.statsTable.put(QUEUE_PUT_SIZE, new StatsItemSet(QUEUE_PUT_SIZE, this.scheduledExecutorService, log)); - this.statsTable.put(QUEUE_GET_NUMS, new StatsItemSet(QUEUE_GET_NUMS, this.scheduledExecutorService, log)); - this.statsTable.put(QUEUE_GET_SIZE, new StatsItemSet(QUEUE_GET_SIZE, this.scheduledExecutorService, log)); + 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(TOPIC_PUT_NUMS, new StatsItemSet(TOPIC_PUT_NUMS, this.scheduledExecutorService, log)); - this.statsTable.put(TOPIC_PUT_SIZE, new StatsItemSet(TOPIC_PUT_SIZE, this.scheduledExecutorService, log)); - this.statsTable.put(GROUP_GET_NUMS, new StatsItemSet(GROUP_GET_NUMS, this.scheduledExecutorService, log)); - this.statsTable.put(GROUP_GET_SIZE, new StatsItemSet(GROUP_GET_SIZE, this.scheduledExecutorService, log)); - this.statsTable.put(GROUP_GET_LATENCY, new StatsItemSet(GROUP_GET_LATENCY, this.scheduledExecutorService, log)); - this.statsTable.put(SNDBCK_PUT_NUMS, new StatsItemSet(SNDBCK_PUT_NUMS, this.scheduledExecutorService, log)); - this.statsTable.put(BROKER_PUT_NUMS, new StatsItemSet(BROKER_PUT_NUMS, this.scheduledExecutorService, log)); - this.statsTable.put(BROKER_GET_NUMS, new StatsItemSet(BROKER_GET_NUMS, this.scheduledExecutorService, log)); - this.statsTable.put(GROUP_GET_FROM_DISK_NUMS, new StatsItemSet(GROUP_GET_FROM_DISK_NUMS, this.scheduledExecutorService, log)); - this.statsTable.put(GROUP_GET_FROM_DISK_SIZE, new StatsItemSet(GROUP_GET_FROM_DISK_SIZE, this.scheduledExecutorService, log)); - this.statsTable.put(BROKER_GET_FROM_DISK_NUMS, new StatsItemSet(BROKER_GET_FROM_DISK_NUMS, this.scheduledExecutorService, log)); - this.statsTable.put(BROKER_GET_FROM_DISK_SIZE, new StatsItemSet(BROKER_GET_FROM_DISK_SIZE, this.scheduledExecutorService, log)); - - this.statsTable.put(COMMERCIAL_SEND_TIMES, new StatsItemSet(COMMERCIAL_SEND_TIMES, this.commercialExecutor, COMMERCIAL_LOG)); - this.statsTable.put(COMMERCIAL_RCV_TIMES, new StatsItemSet(COMMERCIAL_RCV_TIMES, this.commercialExecutor, COMMERCIAL_LOG)); - this.statsTable.put(COMMERCIAL_SEND_SIZE, new StatsItemSet(COMMERCIAL_SEND_SIZE, this.commercialExecutor, COMMERCIAL_LOG)); - this.statsTable.put(COMMERCIAL_RCV_SIZE, new StatsItemSet(COMMERCIAL_RCV_SIZE, this.commercialExecutor, COMMERCIAL_LOG)); - this.statsTable.put(COMMERCIAL_RCV_EPOLLS, new StatsItemSet(COMMERCIAL_RCV_EPOLLS, this.commercialExecutor, COMMERCIAL_LOG)); - this.statsTable.put(COMMERCIAL_SNDBCK_TIMES, new StatsItemSet(COMMERCIAL_SNDBCK_TIMES, this.commercialExecutor, COMMERCIAL_LOG)); - this.statsTable.put(COMMERCIAL_PERM_FAILURES, new StatsItemSet(COMMERCIAL_PERM_FAILURES, this.commercialExecutor, COMMERCIAL_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(GROUP_ACK_NUMS, new StatsItemSet(GROUP_ACK_NUMS, this.scheduledExecutorService, log)); + this.statsTable.put(GROUP_CK_NUMS, new StatsItemSet(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(TOPIC_PUT_LATENCY, new StatsItemSet(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 produerStateGetter.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; + } + }); + } + + private void initScheduleService() { + this.scheduledExecutorService = + Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("BrokerStatsThread", true, brokerConfig)); + this.commercialExecutor = + Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("CommercialStatsThread", true, brokerConfig)); + this.accountExecutor = + Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("AccountStatsThread", true, brokerConfig)); } public MomentStatsItemSet getMomentStatsItemSetFallSize() { @@ -116,6 +296,22 @@ public MomentStatsItemSet getMomentStatsItemSetFallTime() { return momentStatsItemSetFallTime; } + public StateGetter getProduerStateGetter() { + return produerStateGetter; + } + + public void setProduerStateGetter(StateGetter produerStateGetter) { + this.produerStateGetter = produerStateGetter; + } + + public StateGetter getConsumerStateGetter() { + return consumerStateGetter; + } + + public void setConsumerStateGetter(StateGetter consumerStateGetter) { + this.consumerStateGetter = consumerStateGetter; + } + public void start() { } @@ -134,82 +330,116 @@ public StatsItem getStatsItem(final String statsName, final String statsKey) { } public void onTopicDeleted(final String topic) { - this.statsTable.get(TOPIC_PUT_NUMS).delValue(topic); - this.statsTable.get(TOPIC_PUT_SIZE).delValue(topic); + this.statsTable.get(Stats.TOPIC_PUT_NUMS).delValue(topic); + this.statsTable.get(Stats.TOPIC_PUT_SIZE).delValue(topic); if (enableQueueStat) { - this.statsTable.get(QUEUE_PUT_NUMS).delValueByPrefixKey(topic, "@"); - this.statsTable.get(QUEUE_PUT_SIZE).delValueByPrefixKey(topic, "@"); + this.statsTable.get(Stats.QUEUE_PUT_NUMS).delValueByPrefixKey(topic, "@"); + this.statsTable.get(Stats.QUEUE_PUT_SIZE).delValueByPrefixKey(topic, "@"); } - this.statsTable.get(GROUP_GET_NUMS).delValueByPrefixKey(topic, "@"); - this.statsTable.get(GROUP_GET_SIZE).delValueByPrefixKey(topic, "@"); - this.statsTable.get(QUEUE_GET_NUMS).delValueByPrefixKey(topic, "@"); - this.statsTable.get(QUEUE_GET_SIZE).delValueByPrefixKey(topic, "@"); - this.statsTable.get(SNDBCK_PUT_NUMS).delValueByPrefixKey(topic, "@"); - this.statsTable.get(GROUP_GET_LATENCY).delValueByInfixKey(topic, "@"); + this.statsTable.get(Stats.GROUP_GET_NUMS).delValueByPrefixKey(topic, "@"); + this.statsTable.get(Stats.GROUP_GET_SIZE).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.momentStatsItemSetFallSize.delValueByInfixKey(topic, "@"); this.momentStatsItemSetFallTime.delValueByInfixKey(topic, "@"); } public void onGroupDeleted(final String group) { - this.statsTable.get(GROUP_GET_NUMS).delValueBySuffixKey(group, "@"); - this.statsTable.get(GROUP_GET_SIZE).delValueBySuffixKey(group, "@"); + this.statsTable.get(Stats.GROUP_GET_NUMS).delValueBySuffixKey(group, "@"); + this.statsTable.get(Stats.GROUP_GET_SIZE).delValueBySuffixKey(group, "@"); if (enableQueueStat) { - this.statsTable.get(QUEUE_GET_NUMS).delValueBySuffixKey(group, "@"); - this.statsTable.get(QUEUE_GET_SIZE).delValueBySuffixKey(group, "@"); + this.statsTable.get(Stats.QUEUE_GET_NUMS).delValueBySuffixKey(group, "@"); + this.statsTable.get(Stats.QUEUE_GET_SIZE).delValueBySuffixKey(group, "@"); } - this.statsTable.get(SNDBCK_PUT_NUMS).delValueBySuffixKey(group, "@"); - this.statsTable.get(GROUP_GET_LATENCY).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(QUEUE_PUT_NUMS).addValue(buildStatsKey(topic, queueId), 1, 1); + 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(QUEUE_PUT_NUMS).addValue(buildStatsKey(topic, queueId), num, times); + 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(QUEUE_PUT_SIZE).addValue(buildStatsKey(topic, queueId), size, 1); + 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(QUEUE_GET_NUMS).addValue(statsKey, incValue, 1); + 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(QUEUE_GET_SIZE).addValue(statsKey, incValue, 1); + 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(TOPIC_PUT_NUMS).addValue(topic, 1, 1); + this.statsTable.get(Stats.TOPIC_PUT_NUMS).addValue(topic, 1, 1); } public void incTopicPutNums(final String topic, int num, int times) { - this.statsTable.get(TOPIC_PUT_NUMS).addValue(topic, num, times); + this.statsTable.get(Stats.TOPIC_PUT_NUMS).addValue(topic, num, times); } public void incTopicPutSize(final String topic, final int size) { - this.statsTable.get(TOPIC_PUT_SIZE).addValue(topic, size, 1); + 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(GROUP_GET_NUMS).addValue(statsKey, incValue, 1); + 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(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(GROUP_ACK_NUMS).addValue(statsKey, incValue, 1); } public String buildStatsKey(String topic, String group) { @@ -258,7 +488,7 @@ public String buildStatsKey(int queueId, String topic, String group) { public void incGroupGetSize(final String group, final String topic, final int incValue) { final String statsKey = buildStatsKey(topic, group); - this.statsTable.get(GROUP_GET_SIZE).addValue(statsKey, incValue, 1); + 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) { @@ -268,29 +498,88 @@ public void incGroupGetLatency(final String group, final String topic, final int } else { statsKey = buildStatsKey(topic, group); } - this.statsTable.get(GROUP_GET_LATENCY).addRTValue(statsKey, incValue, 1); + 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(TOPIC_PUT_LATENCY).addValue(statsKey.toString(), incValue, 1); } public void incBrokerPutNums() { - this.statsTable.get(BROKER_PUT_NUMS).getAndCreateStatsItem(this.clusterName).getValue().add(1); + 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 incBrokerPutNums(final int incValue) { - this.statsTable.get(BROKER_PUT_NUMS).getAndCreateStatsItem(this.clusterName).getValue().add(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 void incBrokerGetNums(final int incValue) { - this.statsTable.get(BROKER_GET_NUMS).getAndCreateStatsItem(this.clusterName).getValue().add(incValue); + 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(SNDBCK_PUT_NUMS).addValue(statsKey, 1, 1); + this.statsTable.get(Stats.SNDBCK_PUT_NUMS).addValue(statsKey, 1, 1); } public double tpsGroupGetNums(final String group, final String topic) { final String statsKey = buildStatsKey(topic, group); - return this.statsTable.get(GROUP_GET_NUMS).getStatsDataInMinute(statsKey).getTps(); + return this.statsTable.get(Stats.GROUP_GET_NUMS).getStatsDataInMinute(statsKey).getTps(); } public void recordDiskFallBehindTime(final String group, final String topic, final int queueId, @@ -305,12 +594,48 @@ public void recordDiskFallBehindSize(final String group, final String topic, fin this.momentStatsItemSetFallSize.getAndCreateStatsItem(statsKey).getValue().set(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); @@ -323,14 +648,131 @@ public String buildCommercialStatsKey(String owner, String topic, String group, 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); + } + public enum StatsType { SEND_SUCCESS, SEND_FAILURE, + + RCV_SUCCESS, + RCV_EPOLLS, SEND_BACK, + SEND_BACK_TO_DLQ, + + SEND_ORDER, SEND_TIMER, SEND_TRANSACTION, - RCV_SUCCESS, - RCV_EPOLLS, + PERM_FAILURE } } diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/Slot.java b/store/src/main/java/org/apache/rocketmq/store/timer/Slot.java new file mode 100644 index 00000000000..b91193b94af --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/timer/Slot.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +public class Slot { + public static final short SIZE = 32; + public final long timeMs; + 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; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerCheckpoint.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerCheckpoint.java new file mode 100644 index 00000000000..2b17fa24886 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerCheckpoint.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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() { + if (null == this.mappedByteBuffer) { + return; + } + + this.flush(); + + // unmap mappedByteBuffer + UtilAll.cleanBuffer(this.mappedByteBuffer); + + try { + this.fileChannel.close(); + } catch (IOException 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 updateDateVersion(long stateVersion) { + dataVersion.nextVersion(stateVersion); + } + + public DataVersion getDataVersion() { + return dataVersion; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerLog.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerLog.java new file mode 100644 index 00000000000..e0836fef183 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerLog.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.fileSize = fileSize; + this.mappedFileQueue = new MappedFileQueue(storePath, fileSize, null); + } + + 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() { + this.mappedFileQueue.flush(0); + //it seems do not need to call shutdown + } + + // be careful. + // if the format of timerlog changed, this offset has to be changed too + // so dose the batch writing + public int getOffsetForLastUnit() { + + return fileSize - (fileSize - MIN_BLANK_LEN) % UNIT_SIZE - MIN_BLANK_LEN - UNIT_SIZE; + } + +} diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java new file mode 100644 index 00000000000..690f4863e61 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java @@ -0,0 +1,1836 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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.Executors; +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.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.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.MessageStore; +import org.apache.rocketmq.store.PutMessageResult; +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.logfile.MappedFile; +import org.apache.rocketmq.store.metrics.DefaultStoreMetricsManager; +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; + + 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; + this.timerWheel = new TimerWheel( + getTimerWheelPath(storeConfig.getStorePathRootDir()), this.slotsTotal, precisionMs); + this.timerLog = new TimerLog(getTimerLogPath(storeConfig.getStorePathRootDir()), timerLogFileSize); + this.timerMetrics = timerMetrics; + this.timerCheckpoint = timerCheckpoint; + this.lastBrokerRole = storeConfig.getBrokerRole(); + + if (messageStore instanceof DefaultMessageStore) { + scheduler = Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryImpl("TimerScheduledThread", + ((DefaultMessageStore) messageStore).getBrokerIdentity())); + } else { + scheduler = Executors.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.getTimerGetMessageThreadNum(), 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()); + + //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(); + ConsumeQueue cq = (ConsumeQueue) 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; + } + SelectMappedBufferResult bufferCQ = cq.getIndexBuffer(tmpOffset); + if (null == bufferCQ) { + // offset in msg may be greater than offset of cq. + tmpOffset -= 1; + continue; + } + try { + long offsetPyTemp = bufferCQ.getByteBuffer().getLong(); + int sizePyTemp = bufferCQ.getByteBuffer().getInt(); + 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 { + bufferCQ.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; + //first save checkpoint + prepareTimerCheckPoint(); + timerFlushService.shutdown(); + timerLog.shutdown(); + timerCheckpoint.shutdown(); + + enqueuePutQueue.clear(); //avoid blocking + dequeueGetQueue.clear(); //avoid blocking + dequeuePutQueue.clear(); //avoid blocking + + enqueueGetService.shutdown(); + enqueuePutService.shutdown(); + dequeueWarmService.shutdown(); + dequeueGetService.shutdown(); + for (int i = 0; i < dequeueGetMessageServices.length; i++) { + dequeueGetMessageServices[i].shutdown(); + } + for (int i = 0; i < dequeuePutMessageServices.length; i++) { + dequeuePutMessageServices[i].shutdown(); + } + timerWheel.shutdown(false); + + this.scheduler.shutdown(); + 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(); + timerCheckpoint.flush(); + 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 void addMetric(MessageExt msg, int value) { + try { + if (null == msg || null == msg.getProperty(MessageConst.PROPERTY_REAL_TOPIC)) { + return; + } + timerMetrics.addAndGet(msg.getProperty(MessageConst.PROPERTY_REAL_TOPIC), 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; + } + ConsumeQueue cq = (ConsumeQueue) 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; + SelectMappedBufferResult bufferCQ = cq.getIndexBuffer(offset); + if (null == bufferCQ) { + return false; + } + try { + int i = 0; + for (; i < bufferCQ.getSize(); i += ConsumeQueue.CQ_STORE_UNIT_SIZE) { + perfCounterTicks.startTick("enqueue_get"); + try { + long offsetPy = bufferCQ.getByteBuffer().getLong(); + int sizePy = bufferCQ.getByteBuffer().getInt(); + bufferCQ.getByteBuffer().getLong(); //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 / ConsumeQueue.CQ_STORE_UNIT_SIZE)); + 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; + } + } + } + } 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 / ConsumeQueue.CQ_STORE_UNIT_SIZE); + } + currQueueOffset = offset + (i / ConsumeQueue.CQ_STORE_UNIT_SIZE); + return i > 0; + } catch (Exception e) { + LOGGER.error("Unknown exception in enqueuing", e); + } finally { + bufferCQ.release(); + } + return false; + } + + public boolean doEnqueue(long offsetPy, int sizePy, long delayedTime, MessageExt messageExt) { + 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; + } + 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 (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 = null; + bufferLocal.get().position(0); + bufferLocal.get().limit(sizePy); + boolean res = messageStore.getData(offsetPy, sizePy, bufferLocal.get()); + if (res) { + bufferLocal.get().flip(); + msgExt = MessageDecoder.decode(bufferLocal.get(), true, false, false); + } + 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); + } + + int retryNum = 0; + while (retryNum < 3) { + if (null == putMessageResult || null == putMessageResult.getPutMessageStatus()) { + retryNum++; + } else { + switch (putMessageResult.getPutMessageStatus()) { + case PUT_OK: + if (brokerStatsManager != null) { + this.brokerStatsManager.incTopicPutNums(message.getTopic(), 1, 1); + this.brokerStatsManager.incTopicPutSize(message.getTopic(), + putMessageResult.getAppendMessageResult().getWroteBytes()); + this.brokerStatsManager.incBrokerPutNums(message.getTopic(), 1); + } + return PUT_OK; + case SERVICE_NOT_AVAILABLE: + return PUT_NEED_RETRY; + case MESSAGE_ILLEGAL: + case PROPERTIES_SIZE_EXCEEDED: + return PUT_NO_RETRY; + case CREATE_MAPPED_FILE_FAILED: + case FLUSH_DISK_TIMEOUT: + case FLUSH_SLAVE_TIMEOUT: + case OS_PAGE_CACHE_BUSY: + case SLAVE_NOT_AVAILABLE: + case UNKNOWN_ERROR: + default: + retryNum++; + } + } + Thread.sleep(50); + putMessageResult = messageStore.putMessage(message); + LOGGER.warn("Retrying to do put timer msg retryNum:{} putRes:{} msg:{}", retryNum, putMessageResult, message); + } + return PUT_NO_RETRY; + } + + public MessageExtBrokerInner convertMessage(MessageExt msgExt, boolean needRoll) { + 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); + + 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 + 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 (!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); + DefaultStoreMetricsManager.incTimerEnqueueCount(getRealTopic(req.getMsg())); + if (shouldRunningDequeue && req.getDelayTime() < currWriteTimeMs) { + dequeuePutQueue.put(req); + } else { + boolean doEnqueueRes = doEnqueue( + req.getOffsetPy(), req.getSizePy(), req.getDelayTime(), req.getMsg()); + 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); + 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() { + return getServiceThreadName() + 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 doRes = false; + boolean tmpDequeueChangeFlag = false; + try { + while (!isStopped() && !doRes) { + if (!isRunningDequeue()) { + dequeueStatusChangeFlag = true; + tmpDequeueChangeFlag = true; + break; + } + try { + perfCounterTicks.startTick(DEQUEUE_PUT); + DefaultStoreMetricsManager.incTimerDequeueCount(getRealTopic(tr.getMsg())); + addMetric(tr.getMsg(), -1); + MessageExtBrokerInner msg = convert(tr.getMsg(), tr.getEnqueueTime(), needRoll(tr.getMagic())); + doRes = PUT_NEED_RETRY != doPut(msg, needRoll(tr.getMagic())); + while (!doRes && !isStopped()) { + if (!isRunningDequeue()) { + dequeueStatusChangeFlag = true; + tmpDequeueChangeFlag = true; + break; + } + doRes = PUT_NEED_RETRY != doPut(msg, needRoll(tr.getMagic())); + Thread.sleep(500L * precisionMs / 1000); + } + perfCounterTicks.endTick(DEQUEUE_PUT); + } catch (Throwable t) { + LOGGER.info("Unknown error", t); + if (storeConfig.isTimerSkipUnknownError()) { + doRes = true; + } 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) { + 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(uniqueKey)) { + 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"); + long start = System.currentTimeMillis(); + while (!this.isStopped()) { + try { + prepareTimerCheckPoint(); + timerLog.getMappedFileQueue().flush(0); + timerWheel.flush(); + timerCheckpoint.flush(); + if (System.currentTimeMillis() - start > storeConfig.getTimerProgressLogIntervalMs()) { + start = System.currentTimeMillis(); + long tmpQueueOffset = currQueueOffset; + ConsumeQueue cq = (ConsumeQueue) 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(); + waitForRunning(storeConfig.getTimerFlushIntervalMs()); + } catch (Throwable e) { + TimerMessageStore.LOGGER.error("Error occurred in " + getServiceName(), e); + } + } + TimerMessageStore.LOGGER.info(this.getServiceName() + " service end"); + } + } + + 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; + ConsumeQueue cq = (ConsumeQueue) 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) / 1000; + } + 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.updateDateVersion(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; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMetrics.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMetrics.java new file mode 100644 index 00000000000..e7b00cc073c --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMetrics.java @@ -0,0 +1,298 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.google.common.io.Files; +import java.io.BufferedWriter; +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.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; +import org.apache.rocketmq.common.ConfigManager; +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; + +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); + + 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(String topic, int value) { + 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) { + TimerMetricsSerializeWrapper wrapper = new TimerMetricsSerializeWrapper(); + wrapper.setTimingCount(timingCount); + wrapper.setDataVersion(dataVersion); + JSON.writeJSONString(writer, wrapper, SerializerFeature.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)) { + continue; + } + if (topics.contains(topic)) { + continue; + } + + iterator.remove(); + log.info("clean timer metrics, because not in topic config, {}", topic); + } + } + + 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() { + String config = configFilePath(); + String temp = config + ".tmp"; + String backup = config + ".bak"; + BufferedWriter bufferedWriter = null; + try { + File tmpFile = new File(temp); + File parentDirectory = tmpFile.getParentFile(); + if (!parentDirectory.exists()) { + if (!parentDirectory.mkdirs()) { + log.error("Failed to create directory: {}", parentDirectory.getCanonicalPath()); + return; + } + } + + if (!tmpFile.exists()) { + if (!tmpFile.createNewFile()) { + log.error("Failed to create file: {}", tmpFile.getCanonicalPath()); + return; + } + } + bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tmpFile, false), + StandardCharsets.UTF_8)); + write0(bufferedWriter); + bufferedWriter.flush(); + bufferedWriter.close(); + log.debug("Finished writing tmp file: {}", temp); + + File configFile = new File(config); + if (configFile.exists()) { + Files.copy(configFile, new File(backup)); + configFile.delete(); + } + + tmpFile.renameTo(configFile); + } catch (IOException e) { + log.error("Failed to persist {}", temp, e); + } finally { + if (null != bufferedWriter) { + try { + bufferedWriter.close(); + } catch (IOException ignore) { + } + } + } + } + + 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); + } + } + +} diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerRequest.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerRequest.java new file mode 100644 index 00000000000..1dd64f75921 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerRequest.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 long enqueueTime; + private final int magic; + 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 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 + + '}'; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerWheel.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerWheel.java new file mode 100644 index 00000000000..70f82998bc9 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerWheel.java @@ -0,0 +1,209 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 int BLANK = -1, IGNORE = -2; + public final int slotsTotal; + public final int precisionMs; + private String fileName; + private final RandomAccessFile randomAccessFile; + private final FileChannel fileChannel; + private final MappedByteBuffer mappedByteBuffer; + private final ByteBuffer byteBuffer; + private final ThreadLocal localBuffer = new ThreadLocal() { + @Override + protected ByteBuffer initialValue() { + return byteBuffer.duplicate(); + } + }; + private final int wheelLength; + + public TimerWheel(String fileName, int slotsTotal, int precisionMs) throws IOException { + this.slotsTotal = slotsTotal; + this.precisionMs = precisionMs; + this.fileName = fileName; + this.wheelLength = this.slotsTotal * 2 * Slot.SIZE; + + File file = new File(fileName); + UtilAll.ensureDirOK(file.getParent()); + + try { + randomAccessFile = new RandomAccessFile(this.fileName, "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); + fileChannel = randomAccessFile.getChannel(); + mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, wheelLength); + assert wheelLength == mappedByteBuffer.remaining(); + this.byteBuffer = ByteBuffer.allocateDirect(wheelLength); + this.byteBuffer.put(mappedByteBuffer); + } catch (FileNotFoundException e) { + log.error("create file channel " + this.fileName + " Failed. ", e); + throw e; + } catch (IOException e) { + log.error("map file " + this.fileName + " Failed. ", e); + throw e; + } + } + + public void shutdown() { + shutdown(true); + } + + public void shutdown(boolean flush) { + if (flush) + this.flush(); + + // unmap mappedByteBuffer + UtilAll.cleanBuffer(this.mappedByteBuffer); + UtilAll.cleanBuffer(this.byteBuffer); + + try { + this.fileChannel.close(); + } catch (IOException e) { + log.error("Shutdown error in timer wheel", e); + } + } + + public void flush() { + 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(); + } + + 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; + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/util/LibC.java b/store/src/main/java/org/apache/rocketmq/store/util/LibC.java index 8eaa44ab461..4c8e7d45385 100644 --- a/store/src/main/java/org/apache/rocketmq/store/util/LibC.java +++ b/store/src/main/java/org/apache/rocketmq/store/util/LibC.java @@ -25,6 +25,8 @@ 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; @@ -50,4 +52,8 @@ public interface LibC extends Library { int mlockall(int flags); int msync(Pointer p, NativeLong length, int flags); + + int mincore(Pointer p, NativeLong length, byte[] vec); + + int getpagesize(); } diff --git a/store/src/main/java/org/apache/rocketmq/store/util/PerfCounter.java b/store/src/main/java/org/apache/rocketmq/store/util/PerfCounter.java new file mode 100644 index 00000000000..e2a55d63994 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/util/PerfCounter.java @@ -0,0 +1,370 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 errror", getServiceName(), e); + try { + Thread.sleep(1000); + } catch (Throwable ignored) { + + } + } + } + logger.info("{} get stopped", getServiceName()); + } + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/AppendCallbackTest.java b/store/src/test/java/org/apache/rocketmq/store/AppendCallbackTest.java index 715c9d334aa..87bfe85da23 100644 --- a/store/src/test/java/org/apache/rocketmq/store/AppendCallbackTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/AppendCallbackTest.java @@ -25,13 +25,13 @@ 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.CommitLog.MessageExtEncoder; -import org.apache.rocketmq.store.CommitLog.PutMessageContext; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; import org.junit.Before; @@ -53,17 +53,17 @@ public void init() throws Exception { messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 4); messageStoreConfig.setMaxHashSlotNum(100); messageStoreConfig.setMaxIndexNum(100 * 10); - messageStoreConfig.setStorePathRootDir(System.getProperty("user.home") + File.separator + "unitteststore"); - messageStoreConfig.setStorePathCommitLog(System.getProperty("user.home") + File.separator + "unitteststore" + File.separator + "commitlog"); + 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, null); + DefaultMessageStore messageStore = new DefaultMessageStore(messageStoreConfig, null, null, new BrokerConfig(), new ConcurrentHashMap<>()); CommitLog commitLog = new CommitLog(messageStore); - callback = commitLog.new DefaultAppendMessageCallback(1024); + callback = commitLog.new DefaultAppendMessageCallback(); } @After public void destroy() { - UtilAll.deleteFile(new File(System.getProperty("user.home") + File.separator + "unitteststore")); + UtilAll.deleteFile(new File(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore")); } @Test diff --git a/store/src/test/java/org/apache/rocketmq/store/BatchPutMessageTest.java b/store/src/test/java/org/apache/rocketmq/store/BatchPutMessageTest.java index 0d1e2f35b30..43ca38eb484 100644 --- a/store/src/test/java/org/apache/rocketmq/store/BatchPutMessageTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/BatchPutMessageTest.java @@ -17,6 +17,15 @@ package org.apache.rocketmq.store; +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; @@ -30,25 +39,17 @@ import org.junit.Before; import org.junit.Test; -import java.io.File; -import java.net.InetSocketAddress; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +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; public class BatchPutMessageTest { private MessageStore messageStore; - public static final char NAME_VALUE_SEPARATOR = 1; - public static final char PROPERTY_SEPARATOR = 2; - public final static Charset CHARSET_UTF8 = Charset.forName("UTF-8"); + public final static Charset CHARSET_UTF8 = StandardCharsets.UTF_8; @Before public void init() throws Exception { @@ -59,11 +60,11 @@ public void init() throws Exception { } @After - public void destory() { + public void destroy() { messageStore.shutdown(); messageStore.destroy(); - UtilAll.deleteFile(new File(System.getProperty("user.home") + File.separator + "putmessagesteststore")); + UtilAll.deleteFile(new File(System.getProperty("java.io.tmpdir") + File.separator + "putmessagesteststore")); } private MessageStore buildMessageStore() throws Exception { @@ -74,9 +75,11 @@ private MessageStore buildMessageStore() throws Exception { messageStoreConfig.setMaxIndexNum(100 * 10); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setFlushIntervalConsumeQueue(1); - messageStoreConfig.setStorePathRootDir(System.getProperty("user.home") + File.separator + "putmessagesteststore"); - messageStoreConfig.setStorePathCommitLog(System.getProperty("user.home") + File.separator + "putmessagesteststore" + File.separator + "commitlog"); - return new DefaultMessageStore(messageStoreConfig, new BrokerStatsManager("simpleTest", true), new MyMessageArrivingListener(), new BrokerConfig()); + 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 @@ -105,7 +108,7 @@ public void testPutMessages() throws Exception { 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+batchPropLen+1) + msgLengthArr[j - 1]; + msgLengthArr[j] = calMsgLength(msg.getBody().length, topicLength, propertiesLength + batchPropLen + 1) + msgLengthArr[j - 1]; j++; } byte[] batchMessageBody = MessageDecoder.encodeMessages(messages); @@ -113,23 +116,30 @@ public void testPutMessages() throws Exception { messageExtBatch.setTopic(topic); messageExtBatch.setQueueId(queue); messageExtBatch.setBody(batchMessageBody); - messageExtBatch.putUserProperty(batchPropK,batchPropV); + 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(); - - Thread.sleep(3 * 1000); for (long i = 0; i < 10; i++) { - MessageExt messageExt = messageStore.lookMessageByOffset(msgLengthArr[(int) i]); - assertThat(messageExt).isNotNull(); - GetMessageResult result = messageStore.getMessage("batch_write_group", topic, queue, i, 1024 * 1024, null); - assertThat(result).isNotNull(); - assertThat(result.getStatus()).isEqualTo(GetMessageStatus.FOUND); - result.release(); + 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); } } @@ -173,38 +183,53 @@ public void testPutIPv6HostMessages() throws Exception { PutMessageResult putMessageResult = messageStore.putMessages(messageExtBatch); assertThat(putMessageResult.isOk()).isTrue(); - Thread.sleep(3 * 1000); - for (long i = 0; i < 10; i++) { - MessageExt messageExt = messageStore.lookMessageByOffset(msgLengthArr[(int) i]); - assertThat(messageExt).isNotNull(); - GetMessageResult result = messageStore.getMessage("batch_write_group", topic, queue, i, 1024 * 1024, null); - assertThat(result).isNotNull(); - assertThat(result.getStatus()).isEqualTo(GetMessageStatus.FOUND); - result.release(); + 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; + + 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; } @@ -233,7 +258,7 @@ private int calIPv6HostMsgLength(int bodyLength, int topicLength, int properties private class MyMessageArrivingListener implements MessageArrivingListener { @Override public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, - byte[] filterBitMap, Map properties) { + byte[] filterBitMap, Map properties) { } } } diff --git a/store/src/test/java/org/apache/rocketmq/store/ConsumeQueueExtTest.java b/store/src/test/java/org/apache/rocketmq/store/ConsumeQueueExtTest.java index e213a025c84..b1ec617ecfc 100644 --- a/store/src/test/java/org/apache/rocketmq/store/ConsumeQueueExtTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/ConsumeQueueExtTest.java @@ -17,28 +17,27 @@ 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 java.io.File; -import java.util.Random; - import static org.assertj.core.api.Assertions.assertThat; public class ConsumeQueueExtTest { - private static final String topic = "abc"; - private static final int queueId = 0; - private static final String storePath = "." + File.separator + "unit_test_store"; - private static final int bitMapLength = 64; - private static final int unitSizeWithBitMap = ConsumeQueueExt.CqExtUnit.MIN_EXT_UNIT_SIZE + bitMapLength / Byte.SIZE; - private static final int cqExtFileSize = 10 * unitSizeWithBitMap; - private static final int unitCount = 20; + 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, queueId, storePath, cqExtFileSize, bitMapLength + TOPIC, QUEUE_ID, STORE_PATH, CQ_EXT_FILE_SIZE, BIT_MAP_LENGTH ); } @@ -57,7 +56,7 @@ protected ConsumeQueueExt.CqExtUnit genUnit(boolean hasBitMap) { cqExtUnit.setTagsCode(Math.abs((new Random(System.currentTimeMillis())).nextInt())); cqExtUnit.setMsgStoreTime(System.currentTimeMillis()); if (hasBitMap) { - cqExtUnit.setFilterBitMap(genBitMap(bitMapLength)); + cqExtUnit.setFilterBitMap(genBitMap(BIT_MAP_LENGTH)); } return cqExtUnit; @@ -93,10 +92,10 @@ public void testPut() { ConsumeQueueExt consumeQueueExt = genExt(); try { - putSth(consumeQueueExt, true, false, unitCount); + putSth(consumeQueueExt, true, false, UNIT_COUNT); } finally { consumeQueueExt.destroy(); - UtilAll.deleteFile(new File(storePath)); + UtilAll.deleteFile(new File(STORE_PATH)); } } @@ -104,7 +103,7 @@ public void testPut() { public void testGet() { ConsumeQueueExt consumeQueueExt = genExt(); - putSth(consumeQueueExt, false, false, unitCount); + putSth(consumeQueueExt, false, false, UNIT_COUNT); try { // from start. @@ -124,7 +123,7 @@ public void testGet() { } } finally { consumeQueueExt.destroy(); - UtilAll.deleteFile(new File(storePath)); + UtilAll.deleteFile(new File(STORE_PATH)); } } @@ -132,21 +131,21 @@ public void testGet() { public void testGet_invalidAddress() { ConsumeQueueExt consumeQueueExt = genExt(); - putSth(consumeQueueExt, false, true, unitCount); + putSth(consumeQueueExt, false, true, UNIT_COUNT); try { ConsumeQueueExt.CqExtUnit unit = consumeQueueExt.get(0); assertThat(unit).isNull(); - long addr = (cqExtFileSize / unitSizeWithBitMap) * unitSizeWithBitMap; - addr += unitSizeWithBitMap; + 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(storePath)); + UtilAll.deleteFile(new File(STORE_PATH)); } } @@ -154,7 +153,7 @@ public void testGet_invalidAddress() { public void testRecovery() { ConsumeQueueExt putCqExt = genExt(); - putSth(putCqExt, false, true, unitCount); + putSth(putCqExt, false, true, UNIT_COUNT); ConsumeQueueExt loadCqExt = genExt(); @@ -166,25 +165,25 @@ public void testRecovery() { assertThat(loadCqExt.getMinAddress()).isEqualTo(Long.MIN_VALUE); // same unit size. - int countPerFile = (cqExtFileSize - ConsumeQueueExt.END_BLANK_DATA_LENGTH) / unitSizeWithBitMap; + int countPerFile = (CQ_EXT_FILE_SIZE - ConsumeQueueExt.END_BLANK_DATA_LENGTH) / UNIT_SIZE_WITH_BIT_MAP; - int lastFileUnitCount = unitCount % countPerFile; + int lastFileUnitCount = UNIT_COUNT % countPerFile; - int fileCount = unitCount / countPerFile + 1; + int fileCount = UNIT_COUNT / countPerFile + 1; if (lastFileUnitCount == 0) { fileCount -= 1; } if (lastFileUnitCount == 0) { - assertThat(loadCqExt.unDecorate(loadCqExt.getMaxAddress()) % cqExtFileSize).isEqualTo(0); + assertThat(loadCqExt.unDecorate(loadCqExt.getMaxAddress()) % CQ_EXT_FILE_SIZE).isEqualTo(0); } else { assertThat(loadCqExt.unDecorate(loadCqExt.getMaxAddress())) - .isEqualTo(lastFileUnitCount * unitSizeWithBitMap + (fileCount - 1) * cqExtFileSize); + .isEqualTo(lastFileUnitCount * UNIT_SIZE_WITH_BIT_MAP + (fileCount - 1) * CQ_EXT_FILE_SIZE); } } finally { putCqExt.destroy(); loadCqExt.destroy(); - UtilAll.deleteFile(new File(storePath)); + UtilAll.deleteFile(new File(STORE_PATH)); } } @@ -192,13 +191,13 @@ public void testRecovery() { public void testTruncateByMinOffset() { ConsumeQueueExt consumeQueueExt = genExt(); - putSth(consumeQueueExt, false, true, unitCount * 2); + putSth(consumeQueueExt, false, true, UNIT_COUNT * 2); try { // truncate first one file. - long address = consumeQueueExt.decorate((long) (cqExtFileSize * 1.5)); + long address = consumeQueueExt.decorate((long) (CQ_EXT_FILE_SIZE * 1.5)); - long expectMinAddress = consumeQueueExt.decorate(cqExtFileSize); + long expectMinAddress = consumeQueueExt.decorate(CQ_EXT_FILE_SIZE); consumeQueueExt.truncateByMinAddress(address); @@ -207,7 +206,7 @@ public void testTruncateByMinOffset() { assertThat(expectMinAddress).isEqualTo(minAddress); } finally { consumeQueueExt.destroy(); - UtilAll.deleteFile(new File(storePath)); + UtilAll.deleteFile(new File(STORE_PATH)); } } @@ -215,13 +214,13 @@ public void testTruncateByMinOffset() { public void testTruncateByMaxOffset() { ConsumeQueueExt consumeQueueExt = genExt(); - putSth(consumeQueueExt, false, true, unitCount * 2); + putSth(consumeQueueExt, false, true, UNIT_COUNT * 2); try { // truncate, only first 3 files exist. - long address = consumeQueueExt.decorate(cqExtFileSize * 2 + unitSizeWithBitMap); + long address = consumeQueueExt.decorate(CQ_EXT_FILE_SIZE * 2 + UNIT_SIZE_WITH_BIT_MAP); - long expectMaxAddress = address + unitSizeWithBitMap; + long expectMaxAddress = address + UNIT_SIZE_WITH_BIT_MAP; consumeQueueExt.truncateByMaxAddress(address); @@ -230,12 +229,12 @@ public void testTruncateByMaxOffset() { assertThat(expectMaxAddress).isEqualTo(maxAddress); } finally { consumeQueueExt.destroy(); - UtilAll.deleteFile(new File(storePath)); + UtilAll.deleteFile(new File(STORE_PATH)); } } @After public void destroy() { - UtilAll.deleteFile(new File(storePath)); + UtilAll.deleteFile(new File(STORE_PATH)); } } diff --git a/store/src/test/java/org/apache/rocketmq/store/ConsumeQueueTest.java b/store/src/test/java/org/apache/rocketmq/store/ConsumeQueueTest.java index 668c069217d..2e08369bde9 100644 --- a/store/src/test/java/org/apache/rocketmq/store/ConsumeQueueTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/ConsumeQueueTest.java @@ -23,42 +23,56 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; -import java.nio.ByteBuffer; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +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.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.Test; +import org.mockito.Mockito; +import org.junit.Assume; +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[] msgBody = msg.getBytes(); + 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 queueId = 0; - private static final String storePath = "." + File.separator + "unit_test_store"; - private static final int commitLogFileSize = 1024 * 8; - private static final int cqFileSize = 10 * 20; - private static final int cqExtFileSize = 10 * (ConsumeQueueExt.CqExtUnit.MIN_EXT_UNIT_SIZE + 64); + 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 bornHost; - private static SocketAddress StoreHost; + private static SocketAddress storeHost; static { try { - StoreHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); + storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); } catch (UnknownHostException e) { e.printStackTrace(); } try { - BornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); + bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); } catch (UnknownHostException e) { e.printStackTrace(); } @@ -66,16 +80,16 @@ public class ConsumeQueueTest { public MessageExtBrokerInner buildMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); - msg.setTopic(topic); + msg.setTopic(TOPIC); msg.setTags("TAG1"); msg.setKeys("Hello"); - msg.setBody(msgBody); + msg.setBody(MSG_BODY); msg.setKeys(String.valueOf(System.currentTimeMillis())); - msg.setQueueId(queueId); + msg.setQueueId(QUEUE_ID); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); - msg.setStoreHost(StoreHost); - msg.setBornHost(BornHost); + msg.setStoreHost(storeHost); + msg.setBornHost(bornHost); for (int i = 0; i < 1; i++) { msg.putUserProperty(String.valueOf(i), "imagoodperson" + i); } @@ -86,13 +100,13 @@ public MessageExtBrokerInner buildMessage() { public MessageExtBrokerInner buildIPv6HostMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); - msg.setTopic(topic); + msg.setTopic(TOPIC); msg.setTags("TAG1"); msg.setKeys("Hello"); - msg.setBody(msgBody); + msg.setBody(MSG_BODY); msg.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); msg.setKeys(String.valueOf(System.currentTimeMillis())); - msg.setQueueId(queueId); + msg.setQueueId(QUEUE_ID); msg.setSysFlag(0); msg.setBornHostV6Flag(); msg.setStoreHostAddressV6Flag(); @@ -115,16 +129,16 @@ public MessageStoreConfig buildStoreConfig(int commitLogFileSize, int cqFileSize messageStoreConfig.setMappedFileSizeConsumeQueueExt(cqExtFileSize); messageStoreConfig.setMessageIndexEnable(false); messageStoreConfig.setEnableConsumeQueueExt(enableCqExt); - - messageStoreConfig.setStorePathRootDir(storePath); - messageStoreConfig.setStorePathCommitLog(storePath + File.separator + "commitlog"); + messageStoreConfig.setHaListenPort(0); + messageStoreConfig.setStorePathRootDir(STORE_PATH); + messageStoreConfig.setStorePathCommitLog(STORE_PATH + File.separator + "commitlog"); return messageStoreConfig; } protected DefaultMessageStore gen() throws Exception { MessageStoreConfig messageStoreConfig = buildStoreConfig( - commitLogFileSize, cqFileSize, true, cqExtFileSize + COMMIT_LOG_FILE_SIZE, CQ_FILE_SIZE, true, CQ_EXT_FILE_SIZE ); BrokerConfig brokerConfig = new BrokerConfig(); @@ -138,7 +152,7 @@ public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { } } - , brokerConfig); + , brokerConfig, new ConcurrentHashMap<>()); assertThat(master.load()).isTrue(); @@ -149,7 +163,7 @@ public void arriving(String topic, int queueId, long logicOffset, long tagsCode, protected DefaultMessageStore genForMultiQueue() throws Exception { MessageStoreConfig messageStoreConfig = buildStoreConfig( - commitLogFileSize, cqFileSize, true, cqExtFileSize + COMMIT_LOG_FILE_SIZE, CQ_FILE_SIZE, true, CQ_EXT_FILE_SIZE ); messageStoreConfig.setEnableLmq(true); @@ -166,7 +180,7 @@ public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map properties) { } } - , brokerConfig); + , brokerConfig, new ConcurrentHashMap<>()); assertThat(master.load()).isTrue(); @@ -175,7 +189,7 @@ public void arriving(String topic, int queueId, long logicOffset, long tagsCode, return master; } - protected void putMsg(DefaultMessageStore master) throws Exception { + protected void putMsg(DefaultMessageStore master) { long totalMsgs = 200; for (long i = 0; i < totalMsgs; i++) { @@ -187,7 +201,7 @@ protected void putMsg(DefaultMessageStore master) throws Exception { } } - protected void putMsgMultiQueue(DefaultMessageStore master) throws Exception { + protected void putMsgMultiQueue(DefaultMessageStore master) { for (long i = 0; i < 1; i++) { master.putMessage(buildMessageMultiQueue()); } @@ -195,16 +209,16 @@ protected void putMsgMultiQueue(DefaultMessageStore master) throws Exception { private MessageExtBrokerInner buildMessageMultiQueue() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); - msg.setTopic(topic); + msg.setTopic(TOPIC); msg.setTags("TAG1"); msg.setKeys("Hello"); - msg.setBody(msgBody); + msg.setBody(MSG_BODY); msg.setKeys(String.valueOf(System.currentTimeMillis())); - msg.setQueueId(queueId); + msg.setQueueId(QUEUE_ID); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); - msg.setStoreHost(StoreHost); - msg.setBornHost(BornHost); + msg.setStoreHost(storeHost); + msg.setBornHost(bornHost); for (int i = 0; i < 1; i++) { msg.putUserProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH, "%LMQ%123,%LMQ%456"); msg.putUserProperty(String.valueOf(i), "imagoodperson" + i); @@ -242,9 +256,15 @@ public void testPutMessagePositionInfo_buildCQRepeatedly() throws Exception { for (int i = 0; i < totalMessages; i++) { putMsg(messageStore); } - Thread.sleep(5); - ConsumeQueue cq = messageStore.getConsumeQueueTable().get(topic).get(queueId); + + // 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(); @@ -268,27 +288,32 @@ public void testPutMessagePositionInfo_buildCQRepeatedly() throws Exception { messageStore.shutdown(); messageStore.destroy(); } - deleteDirectory(storePath); + 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); } - Thread.sleep(5); - ConsumeQueue cq = messageStore.getConsumeQueueTable().get(topic).get(queueId); - Method method = cq.getClass().getDeclaredMethod("putMessagePositionInfoWrapper", DispatchRequest.class, boolean.class); + // 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(); @@ -301,11 +326,11 @@ public void testPutMessagePositionInfoWrapper_MultiQueue() throws Exception { assertThat(cq).isNotNull(); - Object dispatchResult = method.invoke(cq, dispatchRequest, true); + Object dispatchResult = method.invoke(cq, dispatchRequest); - ConsumeQueue lmqCq1 = messageStore.getConsumeQueueTable().get("%LMQ%123").get(0); + ConsumeQueueInterface lmqCq1 = messageStore.getConsumeQueueTable().get("%LMQ%123").get(0); - ConsumeQueue lmqCq2 = messageStore.getConsumeQueueTable().get("%LMQ%456").get(0); + ConsumeQueueInterface lmqCq2 = messageStore.getConsumeQueueTable().get("%LMQ%456").get(0); assertThat(lmqCq1).isNotNull(); @@ -316,7 +341,7 @@ public void testPutMessagePositionInfoWrapper_MultiQueue() throws Exception { messageStore.shutdown(); messageStore.destroy(); } - deleteDirectory(storePath); + deleteDirectory(STORE_PATH); } } @@ -333,13 +358,18 @@ public void testPutMessagePositionInfoMultiQueue() throws Exception { for (int i = 0; i < totalMessages; i++) { putMsgMultiQueue(messageStore); } - Thread.sleep(5); - ConsumeQueue cq = messageStore.getConsumeQueueTable().get(topic).get(queueId); + // 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); - ConsumeQueue lmqCq1 = messageStore.getConsumeQueueTable().get("%LMQ%123").get(0); + ConsumeQueueInterface lmqCq1 = messageStore.getConsumeQueueTable().get("%LMQ%123").get(0); - ConsumeQueue lmqCq2 = messageStore.getConsumeQueueTable().get("%LMQ%456").get(0); + ConsumeQueueInterface lmqCq2 = messageStore.getConsumeQueueTable().get("%LMQ%456").get(0); assertThat(cq).isNotNull(); @@ -352,7 +382,7 @@ public void testPutMessagePositionInfoMultiQueue() throws Exception { messageStore.shutdown(); messageStore.destroy(); } - deleteDirectory(storePath); + deleteDirectory(STORE_PATH); } } @@ -377,58 +407,100 @@ public void dispatch(DispatchRequest request) { }); try { - try { - putMsg(master); - Thread.sleep(3000L);//wait ConsumeQueue create success. - } catch (Exception e) { - e.printStackTrace(); - assertThat(Boolean.FALSE).isTrue(); - } - ConsumeQueue cq = master.getConsumeQueueTable().get(topic).get(queueId); + 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(); - long index = 0; - - while (index < cq.getMaxOffsetInQueue()) { - SelectMappedBufferResult bufferResult = cq.getIndexBuffer(index); - - assertThat(bufferResult).isNotNull(); - - ByteBuffer buffer = bufferResult.getByteBuffer(); + ReferredIterator bufferResult = cq.iterateFrom(0); - assertThat(buffer).isNotNull(); - try { - ConsumeQueueExt.CqExtUnit cqExtUnit = new ConsumeQueueExt.CqExtUnit(); - for (int i = 0; i < bufferResult.getSize(); i += ConsumeQueue.CQ_STORE_UNIT_SIZE) { - long phyOffset = buffer.getLong(); - int size = buffer.getInt(); - long tagsCode = buffer.getLong(); + assertThat(bufferResult).isNotNull(); - assertThat(phyOffset).isGreaterThanOrEqualTo(0); - assertThat(size).isGreaterThan(0); - assertThat(tagsCode).isLessThan(0); + Assert.assertTrue(bufferResult.hasNext()); - boolean ret = cq.getExt(tagsCode, cqExtUnit); - - assertThat(ret).isTrue(); - assertThat(cqExtUnit).isNotNull(); - assertThat(cqExtUnit.getSize()).isGreaterThan((short) 0); - assertThat(cqExtUnit.getMsgStoreTime()).isGreaterThan(0); - assertThat(cqExtUnit.getTagsCode()).isGreaterThan(0); - } - - } finally { - bufferResult.release(); + 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); } - index += cqFileSize / ConsumeQueue.CQ_STORE_UNIT_SIZE; + } finally { + bufferResult.release(); } + } finally { master.shutdown(); master.destroy(); - UtilAll.deleteFile(new File(storePath)); + 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(); } } diff --git a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java index d8202eb539f..083aabc48b3 100644 --- a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java @@ -17,14 +17,19 @@ package org.apache.rocketmq.store; +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.stats.BrokerStatsManager; import org.junit.After; import org.junit.Before; @@ -45,6 +50,8 @@ 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 @@ -105,10 +112,10 @@ public void testIsSpaceFullMultiCommitLogStorePath() throws Exception { String storePath = config.getStorePathCommitLog(); StringBuilder storePathBuilder = new StringBuilder(); for (int i = 0; i < 3; i++) { - storePathBuilder.append(storePath).append(i).append(MessageStoreConfig.MULTI_PATH_SPLITTER); + storePathBuilder.append(storePath).append(i).append(MixAll.MULTI_PATH_SPLITTER); } config.setStorePathCommitLog(storePathBuilder.toString()); - String[] paths = config.getStorePathCommitLog().trim().split(MessageStoreConfig.MULTI_PATH_SPLITTER); + String[] paths = config.getStorePathCommitLog().trim().split(MixAll.MULTI_PATH_SPLITTER); assertEquals(3, paths.length); initMessageStore(config, diskSpaceCleanForciblyRatio); @@ -332,7 +339,7 @@ public void testDeleteExpiredFilesManually() throws Exception { } } - private DefaultMessageStore.CleanCommitLogService getCleanCommitLogService(double diskSpaceCleanForciblyRatio) + private DefaultMessageStore.CleanCommitLogService getCleanCommitLogService() throws Exception { Field serviceField = messageStore.getClass().getDeclaredField("cleanCommitLogService"); serviceField.setAccessible(true); @@ -340,15 +347,6 @@ private DefaultMessageStore.CleanCommitLogService getCleanCommitLogService(doubl (DefaultMessageStore.CleanCommitLogService) serviceField.get(messageStore); serviceField.setAccessible(false); - Field warningLevelRatioField = cleanCommitLogService.getClass().getDeclaredField("diskSpaceWarningLevelRatio"); - warningLevelRatioField.setAccessible(true); - warningLevelRatioField.set(cleanCommitLogService, diskSpaceCleanForciblyRatio); - warningLevelRatioField.setAccessible(false); - - Field cleanForciblyRatioField = cleanCommitLogService.getClass().getDeclaredField("diskSpaceCleanForciblyRatio"); - cleanForciblyRatioField.setAccessible(true); - cleanForciblyRatioField.set(cleanCommitLogService, diskSpaceCleanForciblyRatio); - cleanForciblyRatioField.setAccessible(false); return cleanCommitLogService; } @@ -364,7 +362,7 @@ private DefaultMessageStore.CleanConsumeQueueService getCleanConsumeQueueService private MappedFileQueue getMappedFileQueueConsumeQueue() throws Exception { - ConsumeQueue consumeQueue = messageStore.getConsumeQueueTable().get(topic).get(queueId); + ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueueTable().get(topic).get(queueId); Field queueField = consumeQueue.getClass().getDeclaredField("mappedFileQueue"); queueField.setAccessible(true); MappedFileQueue fileQueue = (MappedFileQueue) queueField.get(consumeQueue); @@ -476,7 +474,7 @@ private MessageStoreConfig genMessageStoreConfig(String deleteWhen, int diskMaxU messageStoreConfig.setDeleteWhen(deleteWhen); messageStoreConfig.setDiskMaxUsedSpaceRatio(diskMaxUsedSpaceRatio); - String storePathRootDir = System.getProperty("user.home") + File.separator + String storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "DefaultMessageStoreCleanFilesTest-" + UUID.randomUUID(); String storePathCommitLog = storePathRootDir + File.separator + "commitlog"; messageStoreConfig.setStorePathRootDir(storePathRootDir); @@ -486,13 +484,27 @@ private MessageStoreConfig genMessageStoreConfig(String deleteWhen, int diskMaxU private void initMessageStore(MessageStoreConfig messageStoreConfig, double diskSpaceCleanForciblyRatio) throws Exception { messageStore = new DefaultMessageStore(messageStoreConfig, - new BrokerStatsManager("test", true), new MyMessageArrivingListener(), new BrokerConfig()); + new BrokerStatsManager("test", true), new MyMessageArrivingListener(), new BrokerConfig(), new ConcurrentHashMap<>()); - cleanCommitLogService = getCleanCommitLogService(diskSpaceCleanForciblyRatio); + 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 { diff --git a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreShutDownTest.java b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreShutDownTest.java index 788bdbdea9d..515a4845a4e 100644 --- a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreShutDownTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreShutDownTest.java @@ -18,6 +18,7 @@ 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; @@ -70,8 +71,11 @@ public DefaultMessageStore buildMessageStore() throws Exception { messageStoreConfig.setMaxHashSlotNum(10000); messageStoreConfig.setMaxIndexNum(100 * 100); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); - return new DefaultMessageStore(messageStoreConfig, new BrokerStatsManager("simpleTest", true), null, new BrokerConfig()); + 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<>()); } - } diff --git a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java index b565c5c6671..12d1e5723c8 100644 --- a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java @@ -17,6 +17,7 @@ package org.apache.rocketmq.store; +import com.google.common.collect.Sets; import java.io.File; import java.io.RandomAccessFile; import java.lang.reflect.InvocationTargetException; @@ -25,45 +26,64 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; -import java.nio.ByteBuffer; 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.Properties; +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.UtilAll; -import org.apache.rocketmq.common.message.MessageConst; +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.common.MixAll; +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.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; 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.mock; @RunWith(MockitoJUnitRunner.class) public class DefaultMessageStoreTest { - private final String StoreMessage = "Once, there was a chance for me!"; - private int QUEUE_TOTAL = 100; - private AtomicInteger QueueId = new AtomicInteger(0); - private SocketAddress BornHost; - private SocketAddress StoreHost; - private byte[] MessageBody; + 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); + storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123); + bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); messageStore = buildMessageStore(); boolean load = messageStore.load(); @@ -73,15 +93,17 @@ public void init() throws Exception { @Test(expected = OverlappingFileLockException.class) public void test_repeat_restart() throws Exception { - QUEUE_TOTAL = 1; - MessageBody = StoreMessage.getBytes(); + queueTotal = 1; + messageBody = storeMessage.getBytes(); MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 8); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 4); messageStoreConfig.setMaxHashSlotNum(100); messageStoreConfig.setMaxIndexNum(100 * 10); - MessageStore master = new DefaultMessageStore(messageStoreConfig, null, new MyMessageArrivingListener(), new BrokerConfig()); + 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); @@ -106,6 +128,10 @@ public void destroy() { } 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); @@ -113,7 +139,16 @@ private MessageStore buildMessageStore() throws Exception { messageStoreConfig.setMaxIndexNum(100 * 100); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setFlushIntervalConsumeQueue(1); - return new DefaultMessageStore(messageStoreConfig, new BrokerStatsManager("simpleTest", true), new MyMessageArrivingListener(), new BrokerConfig()); + 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 @@ -121,8 +156,8 @@ public void testWriteAndRead() { long ipv4HostMsgs = 10; long ipv6HostMsgs = 10; long totalMsgs = ipv4HostMsgs + ipv6HostMsgs; - QUEUE_TOTAL = 1; - MessageBody = StoreMessage.getBytes(); + queueTotal = 1; + messageBody = storeMessage.getBytes(); for (long i = 0; i < ipv4HostMsgs; i++) { messageStore.putMessage(buildMessage()); } @@ -153,8 +188,8 @@ public void testLookMessageByOffset_OffsetIsFirst() { 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)); + assertThat(new String(messageExt.getBody())).isEqualTo(buildMessageBodyByOffset(storeMessage, firstOffset)); + assertThat(new String(messageExt1.getBody())).isEqualTo(buildMessageBodyByOffset(storeMessage, firstOffset)); } @Test @@ -168,7 +203,7 @@ public void testLookMessageByOffset_OffsetIsLast() { MessageExt messageExt = getDefaultMessageStore().lookMessageByOffset(lastResult.getWroteOffset(), lastResult.getWroteBytes()); - assertThat(new String(messageExt.getBody())).isEqualTo(buildMessageBodyByOffset(StoreMessage, lastIndex)); + assertThat(new String(messageExt.getBody())).isEqualTo(buildMessageBodyByOffset(storeMessage, lastIndex)); } @Test @@ -193,13 +228,12 @@ public void testGetOffsetInQueueByTime() { //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); - ConsumeQueue consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); + ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); for (AppendMessageResult appendMessageResult : appendMessageResults) { long offset = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp()); - SelectMappedBufferResult indexBuffer = consumeQueue.getIndexBuffer(offset); - assertThat(indexBuffer.getByteBuffer().getLong()).isEqualTo(appendMessageResult.getWroteOffset()); - assertThat(indexBuffer.getByteBuffer().getInt()).isEqualTo(appendMessageResult.getWroteBytes()); - indexBuffer.release(); + CqUnit cqUnit = consumeQueue.get(offset); + assertThat(cqUnit.getPos()).isEqualTo(appendMessageResult.getWroteOffset()); + assertThat(cqUnit.getSize()).isEqualTo(appendMessageResult.getWroteBytes()); } } @@ -213,18 +247,12 @@ public void testGetOffsetInQueueByTime_TimestampIsSkewing() { StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); int skewing = 2; - ConsumeQueue consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); + ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); for (AppendMessageResult appendMessageResult : appendMessageResults) { - long offset = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp() + skewing); - long offset2 = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp() - skewing); - SelectMappedBufferResult indexBuffer = consumeQueue.getIndexBuffer(offset); - SelectMappedBufferResult indexBuffer2 = consumeQueue.getIndexBuffer(offset2); - assertThat(indexBuffer.getByteBuffer().getLong()).isEqualTo(appendMessageResult.getWroteOffset()); - assertThat(indexBuffer.getByteBuffer().getInt()).isEqualTo(appendMessageResult.getWroteBytes()); - assertThat(indexBuffer2.getByteBuffer().getLong()).isEqualTo(appendMessageResult.getWroteOffset()); - assertThat(indexBuffer2.getByteBuffer().getInt()).isEqualTo(appendMessageResult.getWroteBytes()); - indexBuffer.release(); - indexBuffer2.release(); + 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()); } } @@ -238,19 +266,12 @@ public void testGetOffsetInQueueByTime_TimestampSkewingIsLarge() { StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); int skewing = 20000; - ConsumeQueue consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); + ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); for (AppendMessageResult appendMessageResult : appendMessageResults) { - long offset = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp() + skewing); - long offset2 = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp() - skewing); - SelectMappedBufferResult indexBuffer = consumeQueue.getIndexBuffer(offset); - SelectMappedBufferResult indexBuffer2 = consumeQueue.getIndexBuffer(offset2); - assertThat(indexBuffer.getByteBuffer().getLong()).isEqualTo(appendMessageResults[totalCount - 1].getWroteOffset()); - assertThat(indexBuffer.getByteBuffer().getInt()).isEqualTo(appendMessageResults[totalCount - 1].getWroteBytes()); - assertThat(indexBuffer2.getByteBuffer().getLong()).isEqualTo(appendMessageResults[0].getWroteOffset()); - assertThat(indexBuffer2.getByteBuffer().getInt()).isEqualTo(appendMessageResults[0].getWroteBytes()); - - indexBuffer.release(); - indexBuffer2.release(); + 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()); } } @@ -310,7 +331,7 @@ public void testGetMessageStoreTimeStamp() { //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); - ConsumeQueue consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId); + 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); @@ -333,13 +354,12 @@ public void testGetStoreTime_EverythingIsOk() { AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, false); //Thread.sleep(10); StoreTestUtil.waitCommitLogReput((DefaultMessageStore) messageStore); - ConsumeQueue consumeQueue = messageStore.getConsumeQueue(topic, queueId); + ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueue(topic, queueId); for (int i = 0; i < totalCount; i++) { - SelectMappedBufferResult indexBuffer = consumeQueue.getIndexBuffer(i); - long storeTime = getStoreTime(indexBuffer); + CqUnit cqUnit = consumeQueue.get(i); + long storeTime = getStoreTime(cqUnit); assertThat(storeTime).isEqualTo(appendMessageResults[i].getStoreTimestamp()); - indexBuffer.release(); } } @@ -347,19 +367,23 @@ public void testGetStoreTime_EverythingIsOk() { public void testGetStoreTime_PhyOffsetIsLessThanCommitLogMinOffset() { long phyOffset = -10; int size = 138; - ByteBuffer byteBuffer = ByteBuffer.allocate(100); - byteBuffer.putLong(phyOffset); - byteBuffer.putInt(size); - byteBuffer.flip(); - MappedFile mappedFile = mock(MappedFile.class); - SelectMappedBufferResult result = new SelectMappedBufferResult(0, byteBuffer, size, mappedFile); - - long storeTime = getStoreTime(result); - result.release(); + CqUnit cqUnit = new CqUnit(0, phyOffset, size, 0); + long storeTime = getStoreTime(cqUnit); assertThat(storeTime).isEqualTo(-1); } + @Test + public void testPutMessage_whenMessagePropertyIsTooLong() { + 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; } @@ -371,7 +395,7 @@ private AppendMessageResult[] putMessages(int totalCount, String topic, int queu 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); + String messageBody = buildMessageBodyByOffset(storeMessage, i); MessageExtBrokerInner msgInner = i < totalCount / 2 ? buildMessage(messageBody.getBytes(), topic) : buildIPv6HostMessage(messageBody.getBytes(), topic); @@ -402,11 +426,11 @@ private String buildMessageBodyByOffset(String message, long i) { return String.format("%s offset %d", message, i); } - private long getStoreTime(SelectMappedBufferResult result) { + private long getStoreTime(CqUnit cqUnit) { try { - Method getStoreTime = getDefaultMessageStore().getClass().getDeclaredMethod("getStoreTime", SelectMappedBufferResult.class); + Method getStoreTime = getDefaultMessageStore().getClass().getDeclaredMethod("getStoreTime", CqUnit.class); getStoreTime.setAccessible(true); - return (long) getStoreTime.invoke(getDefaultMessageStore(), result); + return (long) getStoreTime.invoke(getDefaultMessageStore(), cqUnit); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } @@ -419,11 +443,32 @@ private MessageExtBrokerInner buildMessage(byte[] messageBody, String topic) { msg.setKeys("Hello"); msg.setBody(messageBody); msg.setKeys(String.valueOf(System.currentTimeMillis())); - msg.setQueueId(Math.abs(QueueId.getAndIncrement()) % QUEUE_TOTAL); + msg.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); - msg.setStoreHost(StoreHost); - msg.setBornHost(BornHost); + 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; } @@ -435,7 +480,7 @@ private MessageExtBrokerInner buildIPv6HostMessage(byte[] messageBody, String to msg.setBody(messageBody); msg.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); msg.setKeys(String.valueOf(System.currentTimeMillis())); - msg.setQueueId(Math.abs(QueueId.getAndIncrement()) % QUEUE_TOTAL); + msg.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal); msg.setSysFlag(0); msg.setBornHostV6Flag(); msg.setStoreHostAddressV6Flag(); @@ -457,11 +502,79 @@ private MessageExtBrokerInner buildIPv6HostMessage(byte[] messageBody, String to } private MessageExtBrokerInner buildMessage() { - return buildMessage(MessageBody, "FooBar"); + 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 { + 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"); + return buildIPv6HostMessage(messageBody, "FooBar"); } private void verifyThatMasterIsFunctional(long totalMsgs, MessageStore master) { @@ -511,7 +624,7 @@ public void testPullSize() throws Exception { @Test public void testRecover() throws Exception { String topic = "recoverTopic"; - MessageBody = StoreMessage.getBytes(); + messageBody = storeMessage.getBytes(); for (int i = 0; i < 100; i++) { MessageExtBrokerInner messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); @@ -527,14 +640,15 @@ public void testRecover() throws Exception { //1.just reboot messageStore.shutdown(); - messageStore = buildMessageStore(); + 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 commitlog and reboot normal + //2.damage commit-log and reboot normal for (int i = 0; i < 100; i++) { MessageExtBrokerInner messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); @@ -554,10 +668,10 @@ public void testRecover() throws Exception { messageStore.shutdown(); //damage last message - damageCommitlog(secondLastPhyOffset); + damageCommitLog((DefaultMessageStore) messageStore, secondLastPhyOffset); //reboot - messageStore = buildMessageStore(); + messageStore = buildMessageStore(storeRootDir); load = messageStore.load(); assertTrue(load); messageStore.start(); @@ -583,14 +697,14 @@ public void testRecover() throws Exception { messageStore.shutdown(); //damage last message - damageCommitlog(secondLastPhyOffset); + damageCommitLog((DefaultMessageStore) messageStore, secondLastPhyOffset); //add abort file String fileName = StorePathConfigHelper.getAbortFile(((DefaultMessageStore) messageStore).getMessageStoreConfig().getStorePathRootDir()); File file = new File(fileName); - MappedFile.ensureDirOK(file.getParent()); + UtilAll.ensureDirOK(file.getParent()); file.createNewFile(); - messageStore = buildMessageStore(); + messageStore = buildMessageStore(storeRootDir); load = messageStore.load(); assertTrue(load); messageStore.start(); @@ -622,39 +736,220 @@ private boolean fileExists(String path) { return false; } - private void damageCommitlog(long offset) throws Exception { - MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); + 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); + } + } - FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel(); - MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024 * 1024 * 10); + @Test + public void testPutMsgExceedsMaxLength() { + messageBody = new byte[4 * 1024 * 1024 + 1]; + MessageExtBrokerInner msg = buildMessage(); - 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); + 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(); - mappedByteBuffer.force(); - fileChannel.force(true); - fileChannel.close(); + 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 testCleanUnusedLmqTopic() throws Exception { - String lmqTopic = "%LMQ%123"; + 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(); - messageExtBrokerInner.setTopic("test"); - messageExtBrokerInner.setQueueId(0); - messageExtBrokerInner.getProperties().put(MessageConst.PROPERTY_INNER_MULTI_DISPATCH, lmqTopic); - messageStore.putMessage(messageExtBrokerInner); + 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(), 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()); + } - Thread.sleep(3000); - messageStore.cleanUnusedLmqTopic(lmqTopic); + @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(), 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(); } private class MyMessageArrivingListener implements MessageArrivingListener { diff --git a/store/src/test/java/org/apache/rocketmq/store/FlushDiskWatcherTest.java b/store/src/test/java/org/apache/rocketmq/store/FlushDiskWatcherTest.java index 60f392d4b63..97968f5938a 100644 --- a/store/src/test/java/org/apache/rocketmq/store/FlushDiskWatcherTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/FlushDiskWatcherTest.java @@ -1,13 +1,13 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at - *

    - * http://www.apache.org/licenses/LICENSE-2.0 - *

    + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,13 +17,13 @@ package org.apache.rocketmq.store; -import java.util.LinkedList; -import java.util.List; - 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; diff --git a/store/src/test/java/org/apache/rocketmq/store/GetMessageResultTest.java b/store/src/test/java/org/apache/rocketmq/store/GetMessageResultTest.java new file mode 100644 index 00000000000..98129c26dfe --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/GetMessageResultTest.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/HATest.java b/store/src/test/java/org/apache/rocketmq/store/HATest.java index c82a237774c..38a04358174 100644 --- a/store/src/test/java/org/apache/rocketmq/store/HATest.java +++ b/store/src/test/java/org/apache/rocketmq/store/HATest.java @@ -1,4 +1,4 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -6,7 +6,7 @@ * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,116 +17,158 @@ 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 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.util.Arrays; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.*; +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; -/** - * HATest - * - */ public class HATest { - private final String StoreMessage = "Once, there was a chance for me!"; - private int QUEUE_TOTAL = 100; - private AtomicInteger QueueId = new AtomicInteger(0); - private SocketAddress BornHost; - private SocketAddress StoreHost; - private byte[] MessageBody; + 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("user.home") + File.separator + - UUID.randomUUID().toString().replace("-", ""); + 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); + 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.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(10943); + 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); + messageStore = buildMessageStore(masterMessageStoreConfig, 0L); + slaveMessageStore = buildMessageStore(slaveStoreConfig, 1L); boolean load = messageStore.load(); boolean slaveLoad = slaveMessageStore.load(); - slaveMessageStore.updateHaMasterAddress("127.0.0.1:10912"); assertTrue(load); assertTrue(slaveLoad); messageStore.start(); + + slaveMessageStore.updateHaMasterAddress("127.0.0.1:" + masterMessageStoreConfig.getHaListenPort()); slaveMessageStore.start(); - Thread.sleep(6000L);//because the haClient will wait 5s after the first connectMaster failed,sleep 6s + 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; - QUEUE_TOTAL = 1; - MessageBody = StoreMessage.getBytes(); + 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; - for (int i = 0; i < 100 && isCommitLogAvailable((DefaultMessageStore) messageStore); i++) { - try { - Thread.sleep(100); - } catch (InterruptedException ignored) { - } + }, item -> item); + assertTrue(exist); } + } - for (int i = 0; i < 100 && isCommitLogAvailable((DefaultMessageStore) slaveMessageStore); i++) { - try { - Thread.sleep(100); - } catch (InterruptedException ignored) { - } + @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++) { - GetMessageResult result = slaveMessageStore.getMessage("GROUP_A", "FooBar", 0, i, 1024 * 1024, null); - assertThat(result).isNotNull(); - assertTrue(GetMessageStatus.FOUND.equals(result.getStatus())); - result.release(); + CompletableFuture putResultFuture = messageStore.asyncPutMessage(buildMessage()); + PutMessageResult result = putResultFuture.get(); + assertEquals(PutMessageStatus.FLUSH_SLAVE_TIMEOUT, result.getPutMessageStatus()); } } @Test - public void testSemiSyncReplica() throws Exception { + public void testSemiSyncReplicaWhenSlaveActingMaster() throws Exception { + // SKip MacOS + Assume.assumeFalse(MixAll.isMac()); long totalMsgs = 5; - QUEUE_TOTAL = 1; - MessageBody = StoreMessage.getBytes(); + 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); @@ -136,26 +178,68 @@ public void testSemiSyncReplica() throws Exception { //so direct read from commitLog by physical offset MessageExt slaveMsg = slaveMessageStore.lookMessageByOffset(result.getAppendMessageResult().getWroteOffset()); assertNotNull(slaveMsg); - assertTrue(Arrays.equals(msg.getBody(), slaveMsg.getBody())); + 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 + //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.SLAVE_NOT_AVAILABLE, result.getPutMessageStatus()); + 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 + 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 + 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{ - Thread.sleep(5000L); + public void destroy() throws Exception { + slaveMessageStore.shutdown(); slaveMessageStore.destroy(); messageStore.shutdown(); @@ -164,13 +248,13 @@ public void destroy() throws Exception{ UtilAll.deleteFile(file); } - private MessageStore buildMessageStore(MessageStoreConfig messageStoreConfig,long brokerId) throws Exception { + private MessageStore buildMessageStore(MessageStoreConfig messageStoreConfig, long brokerId) throws Exception { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setBrokerId(brokerId); - return new DefaultMessageStore(messageStoreConfig, brokerStatsManager, null, brokerConfig); + return new DefaultMessageStore(messageStoreConfig, brokerStatsManager, null, brokerConfig, new ConcurrentHashMap<>()); } - private void buildMessageStoreConfig(MessageStoreConfig messageStoreConfig){ + private void buildMessageStoreConfig(MessageStoreConfig messageStoreConfig) { messageStoreConfig.setMappedFileSizeCommitLog(1024 * 1024 * 10); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 1024 * 10); messageStoreConfig.setMaxHashSlotNum(10000); @@ -183,29 +267,28 @@ private MessageExtBrokerInner buildMessage() { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic("FooBar"); msg.setTags("TAG1"); - msg.setBody(MessageBody); + msg.setBody(messageBody); msg.setKeys(String.valueOf(System.currentTimeMillis())); - msg.setQueueId(Math.abs(QueueId.getAndIncrement()) % QUEUE_TOTAL); + msg.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); - msg.setStoreHost(StoreHost); - msg.setBornHost(BornHost); + msg.setStoreHost(storeHost); + msg.setBornHost(bornHost); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); return msg; } - private boolean isCommitLogAvailable(DefaultMessageStore store) { + private boolean isCommitLogAvailable(DefaultMessageStore store) { try { - Field serviceField = store.getClass().getDeclaredField("reputMessageService"); serviceField.setAccessible(true); DefaultMessageStore.ReputMessageService reputService = - (DefaultMessageStore.ReputMessageService) serviceField.get(store); + (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 ) { + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { throw new RuntimeException(e); } } diff --git a/store/src/test/java/org/apache/rocketmq/store/MappedFileQueueTest.java b/store/src/test/java/org/apache/rocketmq/store/MappedFileQueueTest.java index 8f76051d1f8..d92b3cbc0d9 100644 --- a/store/src/test/java/org/apache/rocketmq/store/MappedFileQueueTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/MappedFileQueueTest.java @@ -17,22 +17,43 @@ package org.apache.rocketmq.store; -import java.io.File; -import java.nio.ByteBuffer; -import java.util.Arrays; +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("target/unit_test_store/a/", 1024, null); + new MappedFileQueue(storePath + File.separator + "a/", 1024, null); for (int i = 0; i < 1024; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); @@ -50,7 +71,7 @@ public void testFindMappedFileByOffset() { final String fixedMsg = "abcd"; MappedFileQueue mappedFileQueue = - new MappedFileQueue("target/unit_test_store/b/", 1024, null); + new MappedFileQueue(storePath + File.separator + "b/", 1024, null); for (int i = 0; i < 1024; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); @@ -98,7 +119,7 @@ public void testFindMappedFileByOffset() { @Test public void testFindMappedFileByOffset_StartOffsetIsNonZero() { MappedFileQueue mappedFileQueue = - new MappedFileQueue("target/unit_test_store/b/", 1024, null); + new MappedFileQueue(storePath + File.separator + "b/", 1024, null); //Start from a non-zero offset MappedFile mappedFile = mappedFileQueue.getLastMappedFile(1024); @@ -122,7 +143,7 @@ public void testAppendMessage() { final String fixedMsg = "0123456789abcdef"; MappedFileQueue mappedFileQueue = - new MappedFileQueue("target/unit_test_store/c/", 1024, null); + new MappedFileQueue(storePath + File.separator + "c/", 1024, null); for (int i = 0; i < 1024; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); @@ -157,7 +178,7 @@ public void testGetMappedMemorySize() { final String fixedMsg = "abcd"; MappedFileQueue mappedFileQueue = - new MappedFileQueue("target/unit_test_store/d/", 1024, null); + new MappedFileQueue(storePath + File.separator + "d/", 1024, null); for (int i = 0; i < 1024; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); @@ -173,7 +194,7 @@ public void testGetMappedMemorySize() { @Test public void testDeleteExpiredFileByOffset() { MappedFileQueue mappedFileQueue = - new MappedFileQueue("target/unit_test_store/e", 5120, null); + new MappedFileQueue(storePath + File.separator + "e/", 5120, null); for (int i = 0; i < 2048; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); @@ -205,7 +226,7 @@ public void testDeleteExpiredFileByOffset() { @Test public void testDeleteExpiredFileByTime() throws Exception { MappedFileQueue mappedFileQueue = - new MappedFileQueue("target/unit_test_store/f/", 1024, null); + new MappedFileQueue(storePath + File.separator + "f/", 1024, null); for (int i = 0; i < 100; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); @@ -215,27 +236,28 @@ public void testDeleteExpiredFileByTime() throws Exception { } assertThat(mappedFileQueue.getMappedFiles().size()).isEqualTo(50); - long expiredTime = 100 * 1000; + long expiredTime = 100 * 1000; for (int i = 0; i < mappedFileQueue.getMappedFiles().size(); i++) { - MappedFile mappedFile = mappedFileQueue.getMappedFiles().get(i); - if (i < 5) { - mappedFile.getFile().setLastModified(System.currentTimeMillis() - expiredTime * 2); - } - if (i > 20) { - mappedFile.getFile().setLastModified(System.currentTimeMillis() - expiredTime * 2); - } - } - mappedFileQueue.deleteExpiredFileByTime(expiredTime, 0, 0, false); + 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("target/unit_test_store/g/", 1024, null); - for (int i =0 ; i < 3; i++) { + new MappedFileQueue(storePath + File.separator + "g/", 1024, null); + for (int i = 0; i < 3; i++) { MappedFile mappedFile = mappedFileQueue.getLastMappedFile(1024 * i); - mappedFile.wrotePosition.set(1024); + mappedFile.setWrotePosition(1024); } assertThat(mappedFileQueue.findMappedFileByOffset(1028).getFileFromOffset()).isEqualTo(1024); @@ -247,9 +269,217 @@ public void testFindMappedFile_ByIteration() { 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); + } + @After - public void destory() { - File file = new File("target/unit_test_store"); + public void destroy() { + File file = new File(storePath); UtilAll.deleteFile(file); } } diff --git a/store/src/test/java/org/apache/rocketmq/store/MappedFileTest.java b/store/src/test/java/org/apache/rocketmq/store/MappedFileTest.java index 50d0ae47f03..a506d44584d 100644 --- a/store/src/test/java/org/apache/rocketmq/store/MappedFileTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/MappedFileTest.java @@ -22,8 +22,8 @@ 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; @@ -34,7 +34,7 @@ public class MappedFileTest { @Test public void testSelectMappedBuffer() throws IOException { - MappedFile mappedFile = new MappedFile("target/unit_test_store/MappedFileTest/000", 1024 * 64); + DefaultMappedFile mappedFile = new DefaultMappedFile("target/unit_test_store/MappedFileTest/000", 1024 * 64); boolean result = mappedFile.appendMessage(storeMessage.getBytes()); assertThat(result).isTrue(); @@ -53,7 +53,7 @@ public void testSelectMappedBuffer() throws IOException { } @After - public void destory() { + public void destroy() { File file = new File("target/unit_test_store"); UtilAll.deleteFile(file); } diff --git a/store/src/test/java/org/apache/rocketmq/store/MultiDispatchTest.java b/store/src/test/java/org/apache/rocketmq/store/MultiDispatchTest.java index 45e4d06652d..85626a332e9 100644 --- a/store/src/test/java/org/apache/rocketmq/store/MultiDispatchTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/MultiDispatchTest.java @@ -21,22 +21,27 @@ import java.net.InetSocketAddress; import java.nio.charset.Charset; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.rocketmq.common.BrokerConfig; 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.MessageStoreConfig; import org.junit.After; import org.junit.Before; import org.junit.Test; +import static org.apache.rocketmq.store.config.StorePathConfigHelper.getStorePathConsumeQueue; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class MultiDispatchTest { - private CommitLog commitLog; - private MultiDispatch multiDispatch; + private ConsumeQueue consumeQueue; + + private DefaultMessageStore messageStore; @Before public void init() throws Exception { @@ -45,54 +50,56 @@ public void init() throws Exception { messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 4); messageStoreConfig.setMaxHashSlotNum(100); messageStoreConfig.setMaxIndexNum(100 * 10); - messageStoreConfig.setStorePathRootDir(System.getProperty("user.home") + File.separator + "unitteststore1"); + messageStoreConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore1"); messageStoreConfig.setStorePathCommitLog( - System.getProperty("user.home") + File.separator + "unitteststore1" + File.separator + "commitlog"); + System.getProperty("java.io.tmpdir") + File.separator + "unitteststore1" + File.separator + "commitlog"); messageStoreConfig.setEnableLmq(true); messageStoreConfig.setEnableMultiDispatch(true); + BrokerConfig brokerConfig = new BrokerConfig(); //too much reference - DefaultMessageStore messageStore = new DefaultMessageStore(messageStoreConfig, null, null, null); - this.commitLog = new CommitLog(messageStore); - this.multiDispatch = new MultiDispatch(messageStore, commitLog); + messageStore = new DefaultMessageStore(messageStoreConfig, null, null, brokerConfig, new ConcurrentHashMap<>()); + consumeQueue = new ConsumeQueue("xxx", 0, + getStorePathConsumeQueue(messageStoreConfig.getStorePathRootDir()), messageStoreConfig.getMappedFileSizeConsumeQueue(), messageStore); } @After public void destroy() { - UtilAll.deleteFile(new File(System.getProperty("user.home") + File.separator + "unitteststore1")); + UtilAll.deleteFile(new File(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore1")); } @Test public void queueKey() { MessageExtBrokerInner messageExtBrokerInner = mock(MessageExtBrokerInner.class); when(messageExtBrokerInner.getQueueId()).thenReturn(2); - String ret = multiDispatch.queueKey("%LMQ%lmq123", messageExtBrokerInner); + String ret = consumeQueue.queueKey("%LMQ%lmq123", messageExtBrokerInner); assertEquals(ret, "%LMQ%lmq123-0"); } @Test public void wrapMultiDispatch() { - MessageExtBrokerInner messageExtBrokerInner = mock(MessageExtBrokerInner.class); - when(messageExtBrokerInner.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH)).thenReturn( - "%LMQ%123,%LMQ%456"); - when(messageExtBrokerInner.getTopic()).thenReturn("test"); - when(messageExtBrokerInner.getBody()).thenReturn("aaa".getBytes(Charset.forName("UTF-8"))); - when(messageExtBrokerInner.getBornHost()).thenReturn(new InetSocketAddress("127.0.0.1", 54270)); - when(messageExtBrokerInner.getStoreHost()).thenReturn(new InetSocketAddress("127.0.0.1", 10911)); - multiDispatch.wrapMultiDispatch(messageExtBrokerInner); - assertTrue(commitLog.getLmqTopicQueueTable().size() == 2); - assertTrue(commitLog.getLmqTopicQueueTable().get("%LMQ%123-0") == 0L); - assertTrue(commitLog.getLmqTopicQueueTable().get("%LMQ%456-0") == 0L); + MessageExtBrokerInner messageExtBrokerInner = buildMessageMultiQueue(); + messageStore.assignOffset(messageExtBrokerInner); + assertEquals(messageExtBrokerInner.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET), "0,0"); } - @Test - public void updateMultiQueueOffset() { - MessageExtBrokerInner messageExtBrokerInner = mock(MessageExtBrokerInner.class); - when(messageExtBrokerInner.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH)).thenReturn("%LMQ%123,%LMQ%456"); - when(messageExtBrokerInner.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET)).thenReturn("0,1"); - multiDispatch.updateMultiQueueOffset(messageExtBrokerInner); - assertTrue(commitLog.getLmqTopicQueueTable().size() == 2); - assertTrue(commitLog.getLmqTopicQueueTable().get("%LMQ%123-0") == 1L); - assertTrue(commitLog.getLmqTopicQueueTable().get("%LMQ%456-0") == 2L); + private MessageExtBrokerInner buildMessageMultiQueue() { + MessageExtBrokerInner msg = new MessageExtBrokerInner(); + msg.setTopic("test"); + msg.setTags("TAG1"); + msg.setKeys("Hello"); + msg.setBody("aaa".getBytes(Charset.forName("UTF-8"))); + msg.setKeys(String.valueOf(System.currentTimeMillis())); + msg.setQueueId(0); + msg.setSysFlag(0); + msg.setBornTimestamp(System.currentTimeMillis()); + msg.setStoreHost(new InetSocketAddress("127.0.0.1", 54270)); + msg.setBornHost(new InetSocketAddress("127.0.0.1", 10911)); + for (int i = 0; i < 1; i++) { + msg.putUserProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH, "%LMQ%123,%LMQ%456"); + } + msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); + + return msg; } } \ No newline at end of file diff --git a/store/src/test/java/org/apache/rocketmq/store/MultiPathMappedFileQueueTest.java b/store/src/test/java/org/apache/rocketmq/store/MultiPathMappedFileQueueTest.java index 66b3f93b01f..07037aa03c8 100644 --- a/store/src/test/java/org/apache/rocketmq/store/MultiPathMappedFileQueueTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/MultiPathMappedFileQueueTest.java @@ -20,8 +20,10 @@ 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; @@ -32,11 +34,11 @@ public void testGetLastMappedFile() { final byte[] fixedMsg = new byte[1024]; MessageStoreConfig config = new MessageStoreConfig(); - config.setStorePathCommitLog("target/unit_test_store/a/" + MessageStoreConfig.MULTI_PATH_SPLITTER - + "target/unit_test_store/b/" + MessageStoreConfig.MULTI_PATH_SPLITTER + 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(MessageStoreConfig.MULTI_PATH_SPLITTER); + 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(); @@ -54,11 +56,11 @@ public void testLoadReadOnlyMappedFiles() { //create old mapped files final byte[] fixedMsg = new byte[1024]; MessageStoreConfig config = new MessageStoreConfig(); - config.setStorePathCommitLog("target/unit_test_store/a/" + MessageStoreConfig.MULTI_PATH_SPLITTER - + "target/unit_test_store/b/" + MessageStoreConfig.MULTI_PATH_SPLITTER + 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(MessageStoreConfig.MULTI_PATH_SPLITTER); + 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(); @@ -72,7 +74,7 @@ public void testLoadReadOnlyMappedFiles() { // test load and readonly MessageStoreConfig config = new MessageStoreConfig(); config.setStorePathCommitLog("target/unit_test_store/b/"); - config.setReadOnlyCommitLogStorePaths("target/unit_test_store/a" + MessageStoreConfig.MULTI_PATH_SPLITTER + config.setReadOnlyCommitLogStorePaths("target/unit_test_store/a" + MixAll.MULTI_PATH_SPLITTER + "target/unit_test_store/c"); MultiPathMappedFileQueue mappedFileQueue = new MultiPathMappedFileQueue(config, 1024, null, null); @@ -92,11 +94,11 @@ public void testUpdatePathsOnline() { final byte[] fixedMsg = new byte[1024]; MessageStoreConfig config = new MessageStoreConfig(); - config.setStorePathCommitLog("target/unit_test_store/a/" + MessageStoreConfig.MULTI_PATH_SPLITTER - + "target/unit_test_store/b/" + MessageStoreConfig.MULTI_PATH_SPLITTER + 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(MessageStoreConfig.MULTI_PATH_SPLITTER); + 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(); @@ -105,9 +107,9 @@ public void testUpdatePathsOnline() { assertThat(mappedFile.getFileName().startsWith(storePaths[idx])).isTrue(); if (i == 500) { - config.setStorePathCommitLog("target/unit_test_store/a/" + MessageStoreConfig.MULTI_PATH_SPLITTER + config.setStorePathCommitLog("target/unit_test_store/a/" + MixAll.MULTI_PATH_SPLITTER + "target/unit_test_store/b/"); - storePaths = config.getStorePathCommitLog().trim().split(MessageStoreConfig.MULTI_PATH_SPLITTER); + storePaths = config.getStorePathCommitLog().trim().split(MixAll.MULTI_PATH_SPLITTER); } } mappedFileQueue.shutdown(1000); @@ -120,11 +122,11 @@ public void testFullStorePath() { Set fullStorePath = new HashSet<>(); MessageStoreConfig config = new MessageStoreConfig(); - config.setStorePathCommitLog("target/unit_test_store/a/" + MessageStoreConfig.MULTI_PATH_SPLITTER - + "target/unit_test_store/b/" + MessageStoreConfig.MULTI_PATH_SPLITTER + 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(MessageStoreConfig.MULTI_PATH_SPLITTER); + String[] storePaths = config.getStorePathCommitLog().trim().split(MixAll.MULTI_PATH_SPLITTER); assertThat(storePaths.length).isEqualTo(3); MappedFile mappedFile = mappedFileQueue.getLastMappedFile(0); diff --git a/store/src/test/java/org/apache/rocketmq/store/ScheduleMessageServiceTest.java b/store/src/test/java/org/apache/rocketmq/store/ScheduleMessageServiceTest.java deleted file mode 100644 index 1c0451ccc9d..00000000000 --- a/store/src/test/java/org/apache/rocketmq/store/ScheduleMessageServiceTest.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.rocketmq.common.BrokerConfig; -import org.apache.rocketmq.common.ThreadFactoryImpl; -import org.apache.rocketmq.common.message.MessageConst; -import org.apache.rocketmq.common.message.MessageExt; -import org.apache.rocketmq.store.config.FlushDiskType; -import org.apache.rocketmq.store.config.MessageStoreConfig; -import org.apache.rocketmq.store.schedule.ScheduleMessageService; -import org.apache.rocketmq.store.stats.BrokerStatsManager; -import org.junit.Assert; -import org.junit.Test; - -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.Mockito.mock; -import static org.mockito.Mockito.when; - -public class ScheduleMessageServiceTest { - - private Random random = new Random(); - - @Test - public void testCorrectDelayOffset_whenInit() throws Exception { - - ConcurrentMap offsetTable = null; - - DefaultMessageStore defaultMessageStore = new DefaultMessageStore(buildMessageStoreConfig(), - new BrokerStatsManager("simpleTest", true), null, new BrokerConfig()); - ScheduleMessageService scheduleMessageService = new ScheduleMessageService(defaultMessageStore); - scheduleMessageService.parseDelayLevel(); - - ConcurrentMap offsetTable1 = new ConcurrentHashMap<>(); - for (int i = 1; i <= 18; 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())); - } - - scheduleMessageService.correctDelayOffset(); - - offsetTable = (ConcurrentMap) field.get(scheduleMessageService); - - for (long offset : offsetTable.values()) { - assertEquals(0, offset); - } - - } - - private MessageStoreConfig buildMessageStoreConfig() 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); - return messageStoreConfig; - } - - @Test - public void testHandlePutResultTask() throws Exception { - DefaultMessageStore messageStore = mock(DefaultMessageStore.class); - MessageStoreConfig config = buildMessageStoreConfig(); - config.setEnableScheduleMessageStats(false); - config.setEnableScheduleAsyncDeliver(true); - when(messageStore.getMessageStoreConfig()).thenReturn(config); - ScheduleMessageService scheduleMessageService = new ScheduleMessageService(messageStore); - scheduleMessageService.parseDelayLevel(); - - Field field = scheduleMessageService.getClass().getDeclaredField("deliverPendingTable"); - field.setAccessible(true); - Map> deliverPendingTable = - (Map>) field.get(scheduleMessageService); - - field = scheduleMessageService.getClass().getDeclaredField("offsetTable"); - field.setAccessible(true); - ConcurrentMap offsetTable = - (ConcurrentMap) field.get(scheduleMessageService); - for (int i = 1; i <= scheduleMessageService.getMaxDelayLevel(); i++) { - offsetTable.put(i, 0L); - } - - int deliverThreadPoolNums = Runtime.getRuntime().availableProcessors(); - ScheduledExecutorService handleExecutorService = new ScheduledThreadPoolExecutor(deliverThreadPoolNums, - new ThreadFactoryImpl("ScheduleMessageExecutorHandleThread_")); - field = scheduleMessageService.getClass().getDeclaredField("handleExecutorService"); - field.setAccessible(true); - field.set(scheduleMessageService, handleExecutorService); - - field = scheduleMessageService.getClass().getDeclaredField("started"); - field.setAccessible(true); - AtomicBoolean started = (AtomicBoolean) field.get(scheduleMessageService); - started.set(true); - - for (int level = 1; level <= scheduleMessageService.getMaxDelayLevel(); level++) { - ScheduleMessageService.HandlePutResultTask handlePutResultTask = scheduleMessageService.new HandlePutResultTask(level); - handleExecutorService.schedule(handlePutResultTask, 10L, TimeUnit.MILLISECONDS); - } - - MessageExt messageExt = new MessageExt(); - messageExt.putUserProperty("init", "test"); - messageExt.getProperties().put(MessageConst.PROPERTY_REAL_QUEUE_ID, "0"); - when(messageStore.lookMessageByOffset(anyLong(), anyInt())).thenReturn(messageExt); - when(messageStore.putMessage(any())).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, null)); - - int msgNum = 100; - int totalMsgNum = msgNum * scheduleMessageService.getMaxDelayLevel(); - List> putMsgFutrueList = new ArrayList<>(totalMsgNum); - for (int level = 1; level <= scheduleMessageService.getMaxDelayLevel(); level++) { - for (int num = 0; num < msgNum; num++) { - CompletableFuture future = new CompletableFuture<>(); - ScheduleMessageService.PutResultProcess putResultProcess = scheduleMessageService.new PutResultProcess(); - putResultProcess = putResultProcess - .setOffset(num) - .setAutoResend(true) - .setFuture(future) - .thenProcess(); - deliverPendingTable.get(level).add(putResultProcess); - putMsgFutrueList.add(future); - } - } - - Collections.shuffle(putMsgFutrueList); - Random random = new Random(); - for (CompletableFuture future : putMsgFutrueList) { - PutMessageStatus status; - if (random.nextInt(1000) % 2 == 0) { - status = PutMessageStatus.PUT_OK; - } else { - status = PutMessageStatus.OS_PAGECACHE_BUSY; - } - - if (random.nextInt(1000) % 2 == 0) { - PutMessageResult result = new PutMessageResult(status, null); - future.complete(result); - } else { - future.completeExceptionally(new Throwable("complete exceptionally")); - } - } - - Thread.sleep(1000); - for (int level = 1; level <= scheduleMessageService.getMaxDelayLevel(); level++) { - Assert.assertEquals(0, deliverPendingTable.get(level).size()); - Assert.assertEquals(msgNum, offsetTable.get(level).longValue()); - } - - scheduleMessageService.shutdown(); - } -} diff --git a/store/src/test/java/org/apache/rocketmq/store/StoreCheckpointTest.java b/store/src/test/java/org/apache/rocketmq/store/StoreCheckpointTest.java index 3c0d9253afa..9137254798b 100644 --- a/store/src/test/java/org/apache/rocketmq/store/StoreCheckpointTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/StoreCheckpointTest.java @@ -48,7 +48,7 @@ public void testWriteAndRead() throws IOException { } @After - public void destory() { + public void destroy() { File file = new File("target/checkpoint_test"); UtilAll.deleteFile(file); } diff --git a/store/src/test/java/org/apache/rocketmq/store/StoreStatsServiceTest.java b/store/src/test/java/org/apache/rocketmq/store/StoreStatsServiceTest.java index 6e66a4487b6..afecbb2430d 100644 --- a/store/src/test/java/org/apache/rocketmq/store/StoreStatsServiceTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/StoreStatsServiceTest.java @@ -16,10 +16,11 @@ */ 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.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.LongAdder; @@ -89,4 +90,17 @@ public void run() { } } + @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); + } + } \ No newline at end of file diff --git a/store/src/test/java/org/apache/rocketmq/store/StoreTestBase.java b/store/src/test/java/org/apache/rocketmq/store/StoreTestBase.java index 5660de1363d..ae0841b8b8e 100644 --- a/store/src/test/java/org/apache/rocketmq/store/StoreTestBase.java +++ b/store/src/test/java/org/apache/rocketmq/store/StoreTestBase.java @@ -20,6 +20,7 @@ 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; @@ -27,16 +28,20 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; -import java.util.*; +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 int QUEUE_TOTAL = 100; - private AtomicInteger QueueId = new AtomicInteger(0); - private SocketAddress BornHost = new InetSocketAddress("127.0.0.1", 8123); - private SocketAddress StoreHost = BornHost; - private byte[] MessageBody = new byte[1024]; + 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<>(); @@ -51,12 +56,12 @@ protected MessageExtBatch buildBatchMessage(int size) { messageExtBatch.setTopic("StoreTest"); messageExtBatch.setTags("TAG1"); messageExtBatch.setKeys("Hello"); - messageExtBatch.setQueueId(Math.abs(QueueId.getAndIncrement()) % QUEUE_TOTAL); + messageExtBatch.setQueueId(Math.abs(queueId.getAndIncrement()) % QUEUE_TOTAL); messageExtBatch.setSysFlag(0); messageExtBatch.setBornTimestamp(System.currentTimeMillis()); - messageExtBatch.setBornHost(BornHost); - messageExtBatch.setStoreHost(StoreHost); + messageExtBatch.setBornHost(bornHost); + messageExtBatch.setStoreHost(storeHost); List messageList = new ArrayList<>(size); for (int i = 0; i < size; i++) { @@ -73,13 +78,13 @@ protected MessageExtBrokerInner buildMessage() { msg.setTopic("StoreTest"); msg.setTags("TAG1"); msg.setKeys("Hello"); - msg.setBody(MessageBody); + msg.setBody(messageBody); msg.setKeys(String.valueOf(System.currentTimeMillis())); - msg.setQueueId(Math.abs(QueueId.getAndIncrement()) % QUEUE_TOTAL); + msg.setQueueId(Math.abs(queueId.getAndIncrement()) % QUEUE_TOTAL); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); - msg.setStoreHost(StoreHost); - msg.setBornHost(BornHost); + msg.setStoreHost(storeHost); + msg.setBornHost(bornHost); return msg; } @@ -88,10 +93,10 @@ protected MessageExtBatch buildIPv6HostBatchMessage(int size) { messageExtBatch.setTopic("StoreTest"); messageExtBatch.setTags("TAG1"); messageExtBatch.setKeys("Hello"); - messageExtBatch.setBody(MessageBody); + messageExtBatch.setBody(messageBody); messageExtBatch.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); messageExtBatch.setKeys(String.valueOf(System.currentTimeMillis())); - messageExtBatch.setQueueId(Math.abs(QueueId.getAndIncrement()) % QUEUE_TOTAL); + messageExtBatch.setQueueId(Math.abs(queueId.getAndIncrement()) % QUEUE_TOTAL); messageExtBatch.setSysFlag(0); messageExtBatch.setBornHostV6Flag(); messageExtBatch.setStoreHostAddressV6Flag(); @@ -122,10 +127,10 @@ protected MessageExtBrokerInner buildIPv6HostMessage() { msg.setTopic("StoreTest"); msg.setTags("TAG1"); msg.setKeys("Hello"); - msg.setBody(MessageBody); + msg.setBody(messageBody); msg.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0"); msg.setKeys(String.valueOf(System.currentTimeMillis())); - msg.setQueueId(Math.abs(QueueId.getAndIncrement()) % QUEUE_TOTAL); + msg.setQueueId(Math.abs(queueId.getAndIncrement()) % QUEUE_TOTAL); msg.setSysFlag(0); msg.setBornHostV6Flag(); msg.setStoreHostAddressV6Flag(); @@ -145,7 +150,7 @@ protected MessageExtBrokerInner buildIPv6HostMessage() { } public static String createBaseDir() { - String baseDir = System.getProperty("user.home") + File.separator + "unitteststore" + File.separator + UUID.randomUUID(); + 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); @@ -155,7 +160,7 @@ public static String createBaseDir() { public static boolean makeSureFileExists(String fileName) throws Exception { File file = new File(fileName); - MappedFile.ensureDirOK(file.getParent()); + UtilAll.ensureDirOK(file.getParent()); return file.createNewFile(); } diff --git a/store/src/test/java/org/apache/rocketmq/store/StoreTestUtil.java b/store/src/test/java/org/apache/rocketmq/store/StoreTestUtil.java index 59809c27cb3..b2d99c3edba 100644 --- a/store/src/test/java/org/apache/rocketmq/store/StoreTestUtil.java +++ b/store/src/test/java/org/apache/rocketmq/store/StoreTestUtil.java @@ -16,8 +16,13 @@ */ package org.apache.rocketmq.store; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; +import io.openmessaging.storage.dledger.store.file.DefaultMmapFile; +import io.openmessaging.storage.dledger.store.file.MmapFile; +import java.io.IOException; +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; @@ -29,7 +34,7 @@ public class StoreTestUtil { - private static final InternalLogger log = InternalLoggerFactory.getLogger(StoreTestUtil.class); + private static final Logger log = LoggerFactory.getLogger(StoreTestUtil.class); public static boolean isCommitLogAvailable(DefaultMessageStore store) { try { @@ -52,10 +57,10 @@ public static void flushConsumeQueue(DefaultMessageStore store) throws Exception field.setAccessible(true); DefaultMessageStore.FlushConsumeQueueService flushService = (DefaultMessageStore.FlushConsumeQueueService) field.get(store); - final int RETRY_TIMES_OVER = 3; + final int retryTimesOver = 3; Method method = DefaultMessageStore.FlushConsumeQueueService.class.getDeclaredMethod("doFlush", int.class); method.setAccessible(true); - method.invoke(flushService, RETRY_TIMES_OVER); + method.invoke(flushService, retryTimesOver); } @@ -86,4 +91,14 @@ public static void flushConsumeIndex(DefaultMessageStore store) throws NoSuchFie 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(); + } + } } diff --git a/store/src/test/java/org/apache/rocketmq/store/dledger/DLedgerCommitlogTest.java b/store/src/test/java/org/apache/rocketmq/store/dledger/DLedgerCommitlogTest.java index 8ab8a23b471..234273b6afd 100644 --- a/store/src/test/java/org/apache/rocketmq/store/dledger/DLedgerCommitlogTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/dledger/DLedgerCommitlogTest.java @@ -19,28 +19,33 @@ import io.openmessaging.storage.dledger.DLedgerServer; import io.openmessaging.storage.dledger.store.file.DLedgerMmapFileStore; import io.openmessaging.storage.dledger.store.file.MmapFileList; - 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.MessageExtBrokerInner; import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.junit.Assert; import org.junit.Test; +import org.junit.Assume; +import org.apache.rocketmq.common.MixAll; -public class DLedgerCommitlogTest extends MessageStoreTestBase { +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 { @Test public void testTruncateCQ() throws Exception { @@ -54,15 +59,17 @@ public void testTruncateCQ() throws Exception { DLedgerServer dLedgerServer = dLedgerCommitLog.getdLedgerServer(); DLedgerMmapFileStore dLedgerMmapFileStore = (DLedgerMmapFileStore) dLedgerServer.getdLedgerStore(); MmapFileList mmapFileList = dLedgerMmapFileStore.getDataFileList(); - Thread.sleep(2000); + Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); + Assert.assertTrue(success); doPutMessages(messageStore, topic, 0, 2000, 0); - Thread.sleep(100); + 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()); } { @@ -72,13 +79,15 @@ public void testTruncateCQ() throws Exception { DLedgerServer dLedgerServer = dLedgerCommitLog.getdLedgerServer(); DLedgerMmapFileStore dLedgerMmapFileStore = (DLedgerMmapFileStore) dLedgerServer.getdLedgerStore(); MmapFileList mmapFileList = dLedgerMmapFileStore.getDataFileList(); - Thread.sleep(1000); + 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 @@ -87,7 +96,8 @@ public void testTruncateCQ() throws Exception { DLedgerServer dLedgerServer = dLedgerCommitLog.getdLedgerServer(); DLedgerMmapFileStore dLedgerMmapFileStore = (DLedgerMmapFileStore) dLedgerServer.getdLedgerStore(); MmapFileList mmapFileList = dLedgerMmapFileStore.getDataFileList(); - Thread.sleep(1000); + 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)); @@ -96,7 +106,6 @@ public void testTruncateCQ() throws Exception { } } - @Test public void testRecover() throws Exception { String base = createBaseDir(); @@ -105,9 +114,11 @@ public void testRecover() throws Exception { String topic = UUID.randomUUID().toString(); { DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, false, 0); - Thread.sleep(1000); + 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); - Thread.sleep(100); + 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()); @@ -136,20 +147,21 @@ public void testRecover() throws Exception { } } - @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); - Thread.sleep(1000); + 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(); + i < 5 ? buildMessage() : buildIPv6HostMessage(); msgInner.setTopic(topic); msgInner.setQueueId(0); PutMessageResult putMessageResult = messageStore.putMessage(msgInner); @@ -157,7 +169,7 @@ public void testPutAndGetMessage() throws Exception { Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Assert.assertEquals(i, putMessageResult.getAppendMessageResult().getLogicsOffset()); } - Thread.sleep(100); + 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()); @@ -184,7 +196,9 @@ public void testBatchPutAndGetMessage() throws Exception { String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, false, 0); - Thread.sleep(1000); + 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; @@ -192,7 +206,7 @@ public void testBatchPutAndGetMessage() throws Exception { List results = new ArrayList<>(); for (int i = 0; i < repeat; i++) { MessageExtBatch messageExtBatch = - i < repeat / 10 ? buildBatchMessage(batchMessageSize) : buildIPv6HostBatchMessage(batchMessageSize); + i < repeat / 10 ? buildBatchMessage(batchMessageSize) : buildIPv6HostBatchMessage(batchMessageSize); messageExtBatch.setTopic(topic); messageExtBatch.setQueueId(0); PutMessageResult putMessageResult = messageStore.putMessages(messageExtBatch); @@ -200,7 +214,7 @@ public void testBatchPutAndGetMessage() throws Exception { Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Assert.assertEquals(i * batchMessageSize, putMessageResult.getAppendMessageResult().getLogicsOffset()); } - Thread.sleep(100); + 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()); @@ -224,17 +238,20 @@ public void testBatchPutAndGetMessage() throws Exception { @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); - Thread.sleep(1000); + 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(); + i < 5 ? buildMessage() : buildIPv6HostMessage(); msgInner.setTopic(topic); msgInner.setQueueId(0); CompletableFuture futureResult = messageStore.asyncPutMessage(msgInner); @@ -243,7 +260,7 @@ public void testAsyncPutAndGetMessage() throws Exception { Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Assert.assertEquals(i, putMessageResult.getAppendMessageResult().getLogicsOffset()); } - Thread.sleep(100); + 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()); @@ -270,7 +287,9 @@ public void testAsyncBatchPutAndGetMessage() throws Exception { String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); DefaultMessageStore messageStore = createDledgerMessageStore(base, group, "n0", peers, null, false, 0); - Thread.sleep(1000); + 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; @@ -279,7 +298,7 @@ public void testAsyncBatchPutAndGetMessage() throws Exception { List results = new ArrayList<>(); for (int i = 0; i < repeat; i++) { MessageExtBatch messageExtBatch = - i < 5 ? buildBatchMessage(batchMessageSize) : buildIPv6HostBatchMessage(batchMessageSize); + i < 5 ? buildBatchMessage(batchMessageSize) : buildIPv6HostBatchMessage(batchMessageSize); messageExtBatch.setTopic(topic); messageExtBatch.setQueueId(0); CompletableFuture futureResult = messageStore.asyncPutMessages(messageExtBatch); @@ -288,7 +307,7 @@ public void testAsyncBatchPutAndGetMessage() throws Exception { Assert.assertEquals(PutMessageStatus.PUT_OK, putMessageResult.getPutMessageStatus()); Assert.assertEquals(i * batchMessageSize, putMessageResult.getAppendMessageResult().getLogicsOffset()); } - Thread.sleep(100); + 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()); @@ -321,22 +340,17 @@ public void testCommittedPos() throws Exception { msgInner.setTopic(topic); msgInner.setQueueId(0); PutMessageResult putMessageResult = leaderStore.putMessage(msgInner); - Assert.assertEquals(PutMessageStatus.OS_PAGECACHE_BUSY, putMessageResult.getPutMessageStatus()); - - Thread.sleep(1000); + 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); - Thread.sleep(2000); + await().atMost(10, SECONDS).until(followerCatchesUp(followerStore, topic)); Assert.assertEquals(1, leaderStore.getMaxOffsetInQueue(topic, 0)); - Assert.assertEquals(1, followerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertTrue(leaderStore.getCommitLog().getMaxOffset() > 0); - leaderStore.destroy(); followerStore.destroy(); @@ -355,22 +369,19 @@ public void testIPv6HostMsgCommittedPos() throws Exception { msgInner.setTopic(topic); msgInner.setQueueId(0); PutMessageResult putMessageResult = leaderStore.putMessage(msgInner); - Assert.assertEquals(PutMessageStatus.OS_PAGECACHE_BUSY, putMessageResult.getPutMessageStatus()); + Assert.assertEquals(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH, putMessageResult.getPutMessageStatus()); - Thread.sleep(1000); + //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); - Thread.sleep(2000); + await().atMost(10, SECONDS).until(followerCatchesUp(followerStore, topic)); Assert.assertEquals(1, leaderStore.getMaxOffsetInQueue(topic, 0)); - Assert.assertEquals(1, followerStore.getMaxOffsetInQueue(topic, 0)); Assert.assertTrue(leaderStore.getCommitLog().getMaxOffset() > 0); - leaderStore.destroy(); followerStore.destroy(); @@ -378,5 +389,7 @@ public void testIPv6HostMsgCommittedPos() throws Exception { followerStore.shutdown(); } - + private Callable followerCatchesUp(DefaultMessageStore followerStore, String topic) { + return () -> followerStore.getMaxOffsetInQueue(topic, 0) == 1; + } } diff --git a/store/src/test/java/org/apache/rocketmq/store/dledger/DLedgerMultiPathTest.java b/store/src/test/java/org/apache/rocketmq/store/dledger/DLedgerMultiPathTest.java new file mode 100644 index 00000000000..5eb83207322 --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/dledger/DLedgerMultiPathTest.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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; + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/dledger/MessageStoreTestBase.java b/store/src/test/java/org/apache/rocketmq/store/dledger/MessageStoreTestBase.java index 7a77e9545f3..a21806ffcf6 100644 --- a/store/src/test/java/org/apache/rocketmq/store/dledger/MessageStoreTestBase.java +++ b/store/src/test/java/org/apache/rocketmq/store/dledger/MessageStoreTestBase.java @@ -21,12 +21,13 @@ 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.store.MessageExtBrokerInner; +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; @@ -56,9 +57,11 @@ protected DefaultMessageStore createDledgerMessageStore(String base, String grou 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 BrokerConfig(), new ConcurrentHashMap<>()); DLedgerServer dLegerServer = ((DLedgerCommitLog) defaultMessageStore.getCommitLog()).getdLedgerServer(); if (leaderId != null) { dLegerServer.getdLedgerConfig().setEnableLeaderElector(false); @@ -67,7 +70,6 @@ protected DefaultMessageStore createDledgerMessageStore(String base, String grou } else { dLegerServer.getMemberState().changeToFollower(0, leaderId); } - } if (createAbort) { String fileName = StorePathConfigHelper.getAbortFile(storeConfig.getStorePathRootDir()); @@ -108,7 +110,7 @@ protected DefaultMessageStore createMessageStore(String base, boolean createAbor storeConfig.setFlushDiskType(FlushDiskType.ASYNC_FLUSH); DefaultMessageStore defaultMessageStore = new DefaultMessageStore(storeConfig, new BrokerStatsManager("CommitlogTest", true), (topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties) -> { - }, new BrokerConfig()); + }, new BrokerConfig(), new ConcurrentHashMap<>()); if (createAbort) { String fileName = StorePathConfigHelper.getAbortFile(storeConfig.getStorePathRootDir()); diff --git a/store/src/test/java/org/apache/rocketmq/store/dledger/MixCommitlogTest.java b/store/src/test/java/org/apache/rocketmq/store/dledger/MixCommitlogTest.java index 4aaa02908a8..db7b594a73b 100644 --- a/store/src/test/java/org/apache/rocketmq/store/dledger/MixCommitlogTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/dledger/MixCommitlogTest.java @@ -16,28 +16,33 @@ */ 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.Test; -public class MixCommitlogTest extends MessageStoreTestBase { - +import static org.awaitility.Awaitility.await; +public class MixCommitlogTest extends MessageStoreTestBase { @Test public void testFallBehindCQ() throws Exception { - String base = createBaseDir(); + 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(); { DefaultMessageStore originalStore = createMessageStore(base, false); doPutMessages(originalStore, topic, 0, 1000, 0); - Assert.assertEquals(11, originalStore.getMaxPhyOffset()/originalStore.getMessageStoreConfig().getMappedFileSizeCommitLog()); - Thread.sleep(500); + 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()); @@ -50,13 +55,16 @@ public void testFallBehindCQ() throws Exception { } { DefaultMessageStore dledgerStore = createDledgerMessageStore(base, group, "n0", peers, null, true, 0); - Thread.sleep(2000); + 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); - Thread.sleep(500); + 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()); @@ -65,11 +73,9 @@ public void testFallBehindCQ() throws Exception { } } - - @Test public void testPutAndGet() throws Exception { - String base = createBaseDir(); + String base = createBaseDir(); String topic = UUID.randomUUID().toString(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); @@ -78,7 +84,7 @@ public void testPutAndGet() throws Exception { { DefaultMessageStore originalStore = createMessageStore(base, false); doPutMessages(originalStore, topic, 0, 1000, 0); - Thread.sleep(500); + 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()); @@ -89,7 +95,7 @@ public void testPutAndGet() throws Exception { } { DefaultMessageStore recoverOriginalStore = createMessageStore(base, true); - Thread.sleep(500); + 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()); @@ -103,9 +109,10 @@ public void testPutAndGet() throws Exception { Assert.assertEquals(dividedOffset, dLedgerCommitLog.getDividedCommitlogOffset()); Assert.assertEquals(0, dledgerStore.dispatchBehindBytes()); Assert.assertEquals(dividedOffset, dLedgerCommitLog.getMaxOffset()); - Thread.sleep(2000); + Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); + Assert.assertTrue(success); doPutMessages(dledgerStore, topic, 0, 1000, 1000); - Thread.sleep(500); + 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()); @@ -117,9 +124,10 @@ public void testPutAndGet() throws Exception { DLedgerCommitLog dLedgerCommitLog = (DLedgerCommitLog) recoverDledgerStore.getCommitLog(); Assert.assertFalse(dLedgerCommitLog.getdLedgerServer().getdLedgerConfig().isEnableDiskForceClean()); Assert.assertEquals(dividedOffset, dLedgerCommitLog.getDividedCommitlogOffset()); - Thread.sleep(2000); + Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); + Assert.assertTrue(success); doPutMessages(recoverDledgerStore, topic, 0, 1000, 2000); - Thread.sleep(500); + 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()); @@ -130,7 +138,7 @@ public void testPutAndGet() throws Exception { @Test public void testDeleteExpiredFiles() throws Exception { - String base = createBaseDir(); + String base = createBaseDir(); String topic = UUID.randomUUID().toString(); String peers = String.format("n0-localhost:%d", nextPort()); String group = UUID.randomUUID().toString(); @@ -139,7 +147,7 @@ public void testDeleteExpiredFiles() throws Exception { { DefaultMessageStore originalStore = createMessageStore(base, false); doPutMessages(originalStore, topic, 0, 1000, 0); - Thread.sleep(500); + 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()); @@ -154,9 +162,10 @@ public void testDeleteExpiredFiles() throws Exception { Assert.assertTrue(dledgerStore.getMessageStoreConfig().isCleanFileForciblyEnable()); Assert.assertFalse(dLedgerCommitLog.getdLedgerServer().getdLedgerConfig().isEnableDiskForceClean()); Assert.assertEquals(dividedOffset, dLedgerCommitLog.getDividedCommitlogOffset()); - Thread.sleep(2000); + Boolean success = await().atMost(Duration.ofSeconds(4)).until(() -> dLedgerCommitLog.getdLedgerServer().getMemberState().isLeader(), item -> item); + Assert.assertTrue(success); doPutMessages(dledgerStore, topic, 0, 1000, 1000); - Thread.sleep(500); + 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()); diff --git a/store/src/test/java/org/apache/rocketmq/store/ha/FlowMonitorTest.java b/store/src/test/java/org/apache/rocketmq/store/ha/FlowMonitorTest.java new file mode 100644 index 00000000000..32fe495a73f --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/ha/FlowMonitorTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/ha/HAClientTest.java b/store/src/test/java/org/apache/rocketmq/store/ha/HAClientTest.java new file mode 100644 index 00000000000..33b3c541d8b --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/ha/HAClientTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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"); + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/ha/HAServerTest.java b/store/src/test/java/org/apache/rocketmq/store/ha/HAServerTest.java new file mode 100644 index 00000000000..54174ac166c --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/ha/HAServerTest.java @@ -0,0 +1,294 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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(); + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/ha/WaitNotifyObjectTest.java b/store/src/test/java/org/apache/rocketmq/store/ha/WaitNotifyObjectTest.java index 99e44320b3c..35584fd4ed7 100644 --- a/store/src/test/java/org/apache/rocketmq/store/ha/WaitNotifyObjectTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/ha/WaitNotifyObjectTest.java @@ -20,8 +20,6 @@ import org.junit.Assert; import org.junit.Test; -import static org.junit.Assert.*; - public class WaitNotifyObjectTest { @Test public void removeFromWaitingThreadTable() throws Exception { diff --git a/store/src/test/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHATest.java b/store/src/test/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHATest.java new file mode 100644 index 00000000000..27dcff14167 --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHATest.java @@ -0,0 +1,553 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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) { + + 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 put failed. + final PutMessageResult putMessageResult = this.messageStore1.putMessage(buildMessage()); + assertEquals(putMessageResult.getPutMessageStatus(), PutMessageStatus.FLUSH_SLAVE_TIMEOUT); + + // The confirmOffset still don't change, because syncStateSet contains broker2, but broker2 shutdown + assertEquals(confirmOffset, this.messageStore1.getConfirmOffset()); + + // 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(); + + ((AutoSwitchHAService) this.messageStore1.getHaService()).setSyncStateSet(result); + + final PutMessageResult putMessageResult = this.messageStore1.putMessage(buildMessage()); + assertEquals(putMessageResult.getPutMessageStatus(), PutMessageStatus.FLUSH_SLAVE_TIMEOUT); + } + + @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 + 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(), 1570); + 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; + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/ha/autoswitch/EpochFileCacheTest.java b/store/src/test/java/org/apache/rocketmq/store/ha/autoswitch/EpochFileCacheTest.java new file mode 100644 index 00000000000..aef83e934a1 --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/ha/autoswitch/EpochFileCacheTest.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/index/IndexFileTest.java b/store/src/test/java/org/apache/rocketmq/store/index/IndexFileTest.java index 7ad5b38db1d..7b35951b877 100644 --- a/store/src/test/java/org/apache/rocketmq/store/index/IndexFileTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/index/IndexFileTest.java @@ -30,8 +30,8 @@ import static org.assertj.core.api.Assertions.assertThat; public class IndexFileTest { - private final int HASH_SLOT_NUM = 100; - private final int INDEX_NUM = 400; + private static final int HASH_SLOT_NUM = 100; + private static final int INDEX_NUM = 400; @Test public void testPutKey() throws Exception { @@ -62,8 +62,8 @@ public void testSelectPhyOffset() throws Exception { 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, true); + 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); diff --git a/store/src/test/java/org/apache/rocketmq/store/kv/CompactionLogTest.java b/store/src/test/java/org/apache/rocketmq/store/kv/CompactionLogTest.java new file mode 100644 index 00000000000..df3c31c6edf --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/kv/CompactionLogTest.java @@ -0,0 +1,267 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + 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)); + }); + } + +} \ No newline at end of file diff --git a/store/src/test/java/org/apache/rocketmq/store/kv/CompactionPositionMgrTest.java b/store/src/test/java/org/apache/rocketmq/store/kv/CompactionPositionMgrTest.java new file mode 100644 index 00000000000..9206fcc4520 --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/kv/CompactionPositionMgrTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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)); + } +} \ No newline at end of file diff --git a/store/src/test/java/org/apache/rocketmq/store/kv/OffsetMapTest.java b/store/src/test/java/org/apache/rocketmq/store/kv/OffsetMapTest.java new file mode 100644 index 00000000000..e520c6a3bb4 --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/kv/OffsetMapTest.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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)); + } +} \ No newline at end of file diff --git a/store/src/test/java/org/apache/rocketmq/store/logfile/DefaultMappedFileTest.java b/store/src/test/java/org/apache/rocketmq/store/logfile/DefaultMappedFileTest.java new file mode 100644 index 00000000000..c150aae6f0f --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/logfile/DefaultMappedFileTest.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + +} \ No newline at end of file diff --git a/store/src/test/java/org/apache/rocketmq/store/pop/AckMsgTest.java b/store/src/test/java/org/apache/rocketmq/store/pop/AckMsgTest.java new file mode 100644 index 00000000000..b5a3ff6381a --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/pop/AckMsgTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.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()); + } +} \ No newline at end of file diff --git a/store/src/test/java/org/apache/rocketmq/store/pop/BatchAckMsgTest.java b/store/src/test/java/org/apache/rocketmq/store/pop/BatchAckMsgTest.java new file mode 100644 index 00000000000..4bcfcf18be6 --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/pop/BatchAckMsgTest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.fastjson.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()); + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/queue/BatchConsumeMessageTest.java b/store/src/test/java/org/apache/rocketmq/store/queue/BatchConsumeMessageTest.java new file mode 100644 index 00000000000..e3ac1b6bdac --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/queue/BatchConsumeMessageTest.java @@ -0,0 +1,577 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/queue/BatchConsumeQueueTest.java b/store/src/test/java/org/apache/rocketmq/store/queue/BatchConsumeQueueTest.java new file mode 100644 index 00000000000..c6525bd8365 --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/queue/BatchConsumeQueueTest.java @@ -0,0 +1,306 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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<>()); + } + +} diff --git a/store/src/test/java/org/apache/rocketmq/store/queue/ConsumeQueueStoreTest.java b/store/src/test/java/org/apache/rocketmq/store/queue/ConsumeQueueStoreTest.java new file mode 100644 index 00000000000..59e1d08791f --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/queue/ConsumeQueueStoreTest.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.TopicConfig; +import org.apache.rocketmq.common.UtilAll; +import org.apache.rocketmq.common.attribute.CQType; +import org.apache.rocketmq.store.MessageStore; +import org.apache.rocketmq.store.PutMessageResult; +import org.apache.rocketmq.store.PutMessageStatus; +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 static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; + +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")); + } + +} diff --git a/store/src/test/java/org/apache/rocketmq/store/queue/ConsumeQueueTest.java b/store/src/test/java/org/apache/rocketmq/store/queue/ConsumeQueueTest.java new file mode 100644 index 00000000000..c3c8be52ddd --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/queue/ConsumeQueueTest.java @@ -0,0 +1,306 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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 void putMsg(DefaultMessageStore messageStore) throws Exception { + 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]); + 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); + } + messageStore.getQueueStore().destroy(consumeQueue); + } + + @Test + public void testEstimateMessageCountInEmptyConsumeQueue() { + DefaultMessageStore master = null; + try { + master = gen(); + 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 testEstimateMessageCount() { + DefaultMessageStore messageStore = null; + try { + messageStore = gen(); + } catch (Exception e) { + e.printStackTrace(); + assertThat(Boolean.FALSE).isTrue(); + } + + 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 testEstimateMessageCountSample() { + DefaultMessageStore messageStore = null; + try { + messageStore = gen(); + } catch (Exception e) { + e.printStackTrace(); + assertThat(Boolean.FALSE).isTrue(); + } + + 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)); + } + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/queue/QueueTestBase.java b/store/src/test/java/org/apache/rocketmq/store/queue/QueueTestBase.java new file mode 100644 index 00000000000..81dc158db53 --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/queue/QueueTestBase.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.sysflag.MessageSysFlag; +import org.apache.rocketmq.store.ConsumeQueue; +import org.apache.rocketmq.store.DefaultMessageStore; +import org.apache.rocketmq.common.message.MessageExtBrokerInner; +import org.apache.rocketmq.store.MessageStore; +import org.apache.rocketmq.store.StoreTestBase; +import org.apache.rocketmq.store.config.MessageStoreConfig; +import org.apache.rocketmq.store.stats.BrokerStatsManager; + +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; + +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 { + 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(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); + + return new DefaultMessageStore( + messageStoreConfig, + new BrokerStatsManager("simpleTest", true), + (topic, queueId, logicOffset, tagsCode, msgStoreTime, filterBitMap, properties) -> { + }, + new BrokerConfig(), topicConfigTable); + } + + 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; + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/queue/SparseConsumeQueueTest.java b/store/src/test/java/org/apache/rocketmq/store/queue/SparseConsumeQueueTest.java new file mode 100644 index 00000000000..c9e290b5db5 --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/queue/SparseConsumeQueueTest.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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()); + } +} \ No newline at end of file diff --git a/store/src/test/java/stats/BrokerStatsManagerTest.java b/store/src/test/java/org/apache/rocketmq/store/stats/BrokerStatsManagerTest.java similarity index 73% rename from store/src/test/java/stats/BrokerStatsManagerTest.java rename to store/src/test/java/org/apache/rocketmq/store/stats/BrokerStatsManagerTest.java index 137c37d0f68..a602da09395 100644 --- a/store/src/test/java/stats/BrokerStatsManagerTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/stats/BrokerStatsManagerTest.java @@ -15,44 +15,45 @@ * limitations under the License. */ -package stats; +package org.apache.rocketmq.store.stats; -import org.apache.rocketmq.store.stats.BrokerStatsManager; +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.store.stats.BrokerStatsManager.BROKER_PUT_NUMS; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.GROUP_GET_FALL_SIZE; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.GROUP_GET_FALL_TIME; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.GROUP_GET_LATENCY; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.GROUP_GET_NUMS; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.GROUP_GET_SIZE; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.QUEUE_GET_NUMS; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.QUEUE_GET_SIZE; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.QUEUE_PUT_NUMS; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.QUEUE_PUT_SIZE; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.SNDBCK_PUT_NUMS; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.TOPIC_PUT_NUMS; -import static org.apache.rocketmq.store.stats.BrokerStatsManager.TOPIC_PUT_SIZE; +import static org.apache.rocketmq.common.stats.Stats.BROKER_PUT_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_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 String TOPIC = "TOPIC_TEST"; - private Integer QUEUE_ID = 0; - private String GROUP_NAME = "GROUP_TEST"; + 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("DefaultCluster", true); + brokerStatsManager = new BrokerStatsManager(CLUSTER_NAME, true); brokerStatsManager.start(); } @After - public void destory() { + public void destroy() { brokerStatsManager.shutdown(); } @@ -129,7 +130,7 @@ public void testIncGroupGetLatency() { @Test public void testIncBrokerPutNums() { brokerStatsManager.incBrokerPutNums(); - assertThat(brokerStatsManager.getStatsItem(BROKER_PUT_NUMS, "DefaultCluster").getValue().doubleValue()).isEqualTo(1L); + assertThat(brokerStatsManager.getStatsItem(BROKER_PUT_NUMS, CLUSTER_NAME).getValue().doubleValue()).isEqualTo(1L); } @Test @@ -164,7 +165,7 @@ public void testOnTopicDeleted() { } @Test - public void testOnGroupDeleted(){ + public void testOnGroupDeleted() { brokerStatsManager.incGroupGetNums(GROUP_NAME, TOPIC, 1); brokerStatsManager.incGroupGetSize(GROUP_NAME, TOPIC, 100); brokerStatsManager.incQueueGetNums(GROUP_NAME, TOPIC, QUEUE_ID, 1); @@ -185,4 +186,30 @@ public void testOnGroupDeleted(){ Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_FALL_SIZE, "1@" + TOPIC + "@" + GROUP_NAME)); Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_FALL_TIME, "1@" + 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); + } } diff --git a/store/src/test/java/org/apache/rocketmq/store/timer/StoreTestUtils.java b/store/src/test/java/org/apache/rocketmq/store/timer/StoreTestUtils.java new file mode 100644 index 00000000000..2a04392cbda --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/timer/StoreTestUtils.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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(); + } + } + +} diff --git a/store/src/test/java/org/apache/rocketmq/store/timer/TimerCheckPointTest.java b/store/src/test/java/org/apache/rocketmq/store/timer/TimerCheckPointTest.java new file mode 100644 index 00000000000..f72874af2dc --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/timer/TimerCheckPointTest.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/timer/TimerLogTest.java b/store/src/test/java/org/apache/rocketmq/store/timer/TimerLogTest.java new file mode 100644 index 00000000000..112c3ad46b2 --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/timer/TimerLogTest.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + + @After + public void shutdown() { + for (TimerLog timerLog : timerLogs) { + timerLog.shutdown(); + timerLog.getMappedFileQueue().destroy(); + } + for (String baseDir : baseDirs) { + StoreTestUtils.deleteFile(baseDir); + } + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest.java b/store/src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest.java new file mode 100644 index 00000000000..63ec97cdb0b --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest.java @@ -0,0 +1,547 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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.store.ConsumeQueue; +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.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.config.FlushDiskType; +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.Assume; +import org.junit.Before; +import org.junit.Test; + +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; + +public class TimerMessageStoreTest { + private final byte[] msgBody = new byte[1024]; + private static MessageStore messageStore; + 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); + + 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) 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"); + TimerMessageStore timerMessageStore = new TimerMessageStore(messageStore, storeConfig, timerCheckpoint, timerMetrics, null); + messageStore.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); + 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 testTimerFlowControl() throws Exception { + String topic = "TimerTest_testTimerFlowControl"; + + storeConfig.setTimerCongestNumEachSlot(100); + TimerMessageStore timerMessageStore = createTimerMessageStore(null); + 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); + 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); + 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, uniqKey); + 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 testPutDeleteTimerMessage() throws Exception { + String topic = "TimerTest_testPutDeleteTimerMessage"; + + final TimerMessageStore timerMessageStore = createTimerMessageStore(null); + 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); + 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 wrote 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); + 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 wrote back to commitLog and consumeQueue. + await().atMost(5000, 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); + 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); + 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(); + } + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/timer/TimerMetricsTest.java b/store/src/test/java/org/apache/rocketmq/store/timer/TimerMetricsTest.java new file mode 100644 index 00000000000..b7392cc4555 --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/timer/TimerMetricsTest.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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()); + first.addAndGet("AAA", 1000); + first.addAndGet("BBB", 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 + 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); + } +} diff --git a/store/src/test/java/org/apache/rocketmq/store/timer/TimerWheelTest.java b/store/src/test/java/org/apache/rocketmq/store/timer/TimerWheelTest.java new file mode 100644 index 00000000000..10d8cecc1fd --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/timer/TimerWheelTest.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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); + } + } + + +} diff --git a/store/src/test/resources/rmq.logback-test.xml b/store/src/test/resources/rmq.logback-test.xml new file mode 100644 index 00000000000..8695d52d57c --- /dev/null +++ b/store/src/test/resources/rmq.logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/style/rmq_checkstyle.xml b/style/rmq_checkstyle.xml index e93b353aaa1..0fb549b707d 100644 --- a/style/rmq_checkstyle.xml +++ b/style/rmq_checkstyle.xml @@ -73,7 +73,7 @@ - + @@ -101,7 +101,7 @@ - + diff --git a/style/rmq_codeStyle.xml b/style/rmq_codeStyle.xml index 7c7ce54b9c9..1cae9e196bd 100644 --- a/style/rmq_codeStyle.xml +++ b/style/rmq_codeStyle.xml @@ -105,7 +105,7 @@