├── .asf.yaml
├── .github
├── ISSUE_TEMPLATE
│ └── issue_template.md
├── stale.yml
└── workflows
│ ├── codeql-analysis.yml
│ └── tests.yaml
├── .gitignore
├── .golangci.yml
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── NOTICE
├── PULL_REQUEST_TEMPLATE.md
├── README.md
├── admin
├── admin.go
├── option.go
└── response.go
├── api.go
├── benchmark
├── consumer.go
├── main.go
├── message.go
├── producer.go
└── stable.go
├── config.go
├── consumer
├── consumer.go
├── consumer_test.go
├── interceptor.go
├── limiter.go
├── lock.go
├── message_util.go
├── mock_offset_store.go
├── offset_store.go
├── offset_store_test.go
├── option.go
├── option_test.go
├── process_queue.go
├── pull_consumer.go
├── pull_consumer_test.go
├── push_consumer.go
├── push_consumer_test.go
├── statistics.go
├── statistics_test.go
├── strategy.go
└── strategy_test.go
├── docs
├── Introduction.md
├── client-design.gliffy
├── feature.md
├── images
│ └── client-design.png
└── zh
│ ├── native-design_zh.md
│ └── rocketmq-protocol_zh.md
├── errors
└── errors.go
├── examples
├── admin
│ ├── group
│ │ └── main.go
│ └── topic
│ │ └── main.go
├── consumer
│ ├── acl
│ │ └── main.go
│ ├── broadcast
│ │ └── main.go
│ ├── delay
│ │ └── main.go
│ ├── interceptor
│ │ └── main.go
│ ├── namespace
│ │ └── main.go
│ ├── orderly
│ │ └── main.go
│ ├── pull
│ │ ├── poll
│ │ │ └── main.go
│ │ ├── poll_assign
│ │ │ └── main.go
│ │ ├── pull
│ │ │ └── main.go
│ │ └── pull_from
│ │ │ └── main.go
│ ├── retry
│ │ ├── concurrent
│ │ │ └── main.go
│ │ └── order
│ │ │ └── main.go
│ ├── rpc
│ │ └── main.go
│ ├── simple
│ │ └── main.go
│ ├── strategy
│ │ └── main.go
│ ├── tag
│ │ └── main.go
│ ├── tls
│ │ └── main.go
│ └── trace
│ │ └── main.go
└── producer
│ ├── acl
│ └── main.go
│ ├── async
│ └── main.go
│ ├── batch
│ └── main.go
│ ├── delay
│ └── main.go
│ ├── delayms
│ └── main.go
│ ├── interceptor
│ └── main.go
│ ├── namespace
│ └── main.go
│ ├── rpc
│ ├── async
│ │ └── main.go
│ └── sync
│ │ └── main.go
│ ├── simple
│ └── main.go
│ ├── tag
│ └── main.go
│ ├── tls
│ └── main.go
│ ├── trace
│ └── main.go
│ └── transaction
│ └── main.go
├── go.mod
├── go.sum
├── hooks
└── filter_message_hook.go
├── internal
├── callback.go
├── client.go
├── constants.go
├── mock_client.go
├── mock_namesrv.go
├── model.go
├── model_test.go
├── mq_version.go
├── namesrv.go
├── namesrv_test.go
├── perm.go
├── remote
│ ├── codec.go
│ ├── codec_test.go
│ ├── future.go
│ ├── interceptor.go
│ ├── interceptor_test.go
│ ├── mock_remote_client.go
│ ├── remote_client.go
│ ├── remote_client_test.go
│ ├── rpchook.go
│ └── tcp_conn.go
├── request.go
├── request_response_future.go
├── response.go
├── route.go
├── route_test.go
├── trace.go
├── trace_test.go
├── transaction.go
├── utils
│ ├── compression.go
│ ├── compression_test.go
│ ├── errors.go
│ ├── files.go
│ ├── math.go
│ ├── namespace.go
│ ├── net.go
│ ├── net_test.go
│ ├── set.go
│ └── string.go
└── validators.go
├── primitive
├── auth.go
├── base.go
├── base_test.go
├── ctx.go
├── errors.go
├── interceptor.go
├── message.go
├── message_test.go
├── nsresolver.go
├── nsresolver_test.go
├── pool.go
├── result.go
├── result_test.go
└── trace.go
├── producer
├── interceptor.go
├── option.go
├── option_test.go
├── producer.go
├── producer_test.go
├── selector.go
└── selector_test.go
└── rlog
└── log.go
/.asf.yaml:
--------------------------------------------------------------------------------
1 | notifications:
2 | commits: commits@rocketmq.apache.org
3 | issues: commits@rocketmq.apache.org
4 | pullrequests: commits@rocketmq.apache.org
5 | jobs: commits@rocketmq.apache.org
6 | discussions: dev@rocketmq.apache.org
7 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_template.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: ISSUE_TEMPLATE
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | The issue tracker is **ONLY** used for the go client (feature request of RocketMQ 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.
11 |
12 | 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.
13 |
14 | 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 to the following:
15 |
16 | **BUG REPORT**
17 | 1. Please describe the issue you observed:
18 |
19 | - What did you do (The steps to reproduce)?
20 |
21 | - What did you expect to see?
22 |
23 | - What did you see instead?
24 |
25 | 2. Please tell us about your environment:
26 |
27 | - What is your OS?
28 |
29 | - What is your client version?
30 |
31 | - What is your RocketMQ version?
32 |
33 | 3. Other information (e.g. detailed explanation, logs, related issues, suggestions on how to fix, etc):
34 |
35 | **FEATURE REQUEST**
36 |
37 | 1. Please describe the feature you are requesting.
38 |
39 | 2. Provide any additional detail on your proposed use case for this feature.
40 |
41 | 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?
42 |
43 | 4. If there are some sub-tasks using -[] for each subtask and create a corresponding issue to map to the sub task:
44 |
45 | - [sub-task1-issue-number](example_sub_issue1_link_here): sub-task1 description here,
46 | - [sub-task2-issue-number](example_sub_issue2_link_here): sub-task2 description here,
47 | - ...
48 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-stale - https://github.com/probot/stale
2 |
3 | # Number of days of inactivity before an Issue or Pull Request becomes stale
4 | daysUntilStale: 60
5 |
6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
8 | daysUntilClose: 14
9 |
10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
11 | onlyLabels: []
12 |
13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
14 | exemptLabels:
15 | - bug
16 |
17 | # Set to true to ignore issues in a project (defaults to false)
18 | exemptProjects: false
19 |
20 | # Set to true to ignore issues in a milestone (defaults to false)
21 | exemptMilestones: false
22 |
23 | # Set to true to ignore issues with an assignee (defaults to false)
24 | exemptAssignees: false
25 |
26 | # Label to use when marking as stale
27 | staleLabel: stale
28 |
29 | # Comment to post when marking as stale. Set to `false` to disable
30 | markComment: >
31 | This issue has been automatically marked as stale because it has not had
32 | recent activity. It will be closed after 14 days if no further activity
33 | occurs. Thank you for your contributions.
34 |
35 | # Comment to post when removing the stale label.
36 | # unmarkComment: >
37 | # Your comment here.
38 |
39 | # Comment to post when closing a stale Issue or Pull Request.
40 | # closeComment: >
41 | # Your comment here.
42 |
43 | # Limit the number of actions per hour, from 1-30. Default is 30
44 | limitPerRun: 30
45 |
46 | # Limit to only `issues` or `pulls`
47 | # only: issues
48 |
49 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
50 | # pulls:
51 | # daysUntilStale: 30
52 | # markComment: >
53 | # This pull request has been automatically marked as stale because it has not had
54 | # recent activity. It will be closed if no further activity occurs. Thank you
55 | # for your contributions.
56 |
57 | # issues:
58 | # exemptLabels:
59 | # - confirmed
60 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "Code Scanning"
13 |
14 | on:
15 | push:
16 | branches: [ master]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '20 8 * * 5'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 |
28 | strategy:
29 | fail-fast: false
30 | matrix:
31 | language: [ 'go' ]
32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
33 | # Learn more:
34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
35 |
36 | steps:
37 | - name: Checkout repository
38 | uses: actions/checkout@v2
39 |
40 | # Initializes the CodeQL tools for scanning.
41 | - name: Initialize CodeQL
42 | uses: github/codeql-action/init@v1
43 | with:
44 | languages: ${{ matrix.language }}
45 | # If you wish to specify custom queries, you can do so here or in a config file.
46 | # By default, queries listed here will override any specified in a config file.
47 | # Prefix the list here with "+" to use these queries and those in the config file.
48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
49 |
50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
51 | # If this step fails, then you should remove it and run the build manually (see below)
52 | - name: Autobuild
53 | uses: github/codeql-action/autobuild@v1
54 |
55 | # ℹ️ Command-line programs to run using the OS shell.
56 | # 📚 https://git.io/JvXDl
57 |
58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
59 | # and modify them (or add more) to build your code if your project
60 | # uses a compiled language
61 |
62 | #- run: |
63 | # make bootstrap
64 | # make release
65 |
66 | - name: Perform CodeQL Analysis
67 | uses: github/codeql-action/analyze@v1
68 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yaml:
--------------------------------------------------------------------------------
1 | name: Tests
2 | on: [push, pull_request]
3 | jobs:
4 | test:
5 | runs-on: ubuntu-latest
6 | strategy:
7 | fail-fast: false
8 | matrix:
9 | target:
10 | - linux-amd64-integration-1-cpu
11 | - linux-amd64-integration-2-cpu
12 | - linux-amd64-integration-4-cpu
13 | - linux-amd64-unit-4-cpu-race
14 | - linux-386-unit-1-cpu
15 | steps:
16 | - uses: actions/checkout@v2
17 | - uses: actions/setup-go@v2
18 | with:
19 | go-version: "1.17.2"
20 | - run: date
21 | - env:
22 | TARGET: ${{ matrix.target }}
23 | run: |
24 | mkdir "${TARGET}"
25 | export REPORT_DIR=$(realpath ${TARGET})
26 | case "${TARGET}" in
27 | linux-amd64-integration-1-cpu)
28 | GOARCH=amd64 CPU=1 PASSES='integration' RACE='false'
29 | ;;
30 | linux-amd64-integration-2-cpu)
31 | GOARCH=amd64 CPU=2 PASSES='integration' RACE='false'
32 | ;;
33 | linux-amd64-integration-4-cpu)
34 | GOARCH=amd64 CPU=4 PASSES='integration' RACE='false'
35 | ;;
36 | linux-amd64-unit-4-cpu-race)
37 | GOARCH=amd64 PASSES='unit' RACE='true' CPU='4'
38 | ;;
39 | linux-386-unit-1-cpu)
40 | GOARCH=386 PASSES='unit' RACE='false' CPU='1'
41 | ;;
42 | *)
43 | echo "Failed to find target"
44 | exit 1
45 | ;;
46 | esac
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | vendor/
3 | coverage.txt
4 | examples/test
5 | /.vscode
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - "1.13.x"
5 | - "1.16.x"
6 | - "1.18.x"
7 | go_import_path: github.com/apache/rocketmq-client-go/v2
8 | env:
9 | global:
10 | - NAME_SERVER_ADDRESS=127.0.0.1:9876
11 | - BROKER_ADDRESS=127.0.0.1:10911
12 | - TOPIC=test
13 | - GROUP=testGroup
14 | - GO111MODULE=on
15 | - HOME=$TRAVIS_HOME
16 | matrix:
17 | - OS_TYPE=centos OS_VERSION=7
18 |
19 | before_script:
20 | - export PROJECT_PATH=`pwd`
21 | - cd ${TRAVIS_HOME}
22 | - wget https://archive.apache.org/dist/rocketmq/4.6.0/rocketmq-all-4.6.0-bin-release.zip
23 | - unzip rocketmq-all-4.6.0-bin-release.zip
24 | - cd rocketmq-all-4.6.0-bin-release
25 | - perl -i -pe's/-Xms8g -Xmx8g -Xmn4g/-Xms2g -Xmx2g -Xmn1g/g' bin/runbroker.sh
26 | - nohup sh bin/mqnamesrv &
27 | - nohup sh bin/mqbroker -n localhost:9876 &
28 |
29 | script:
30 | - cd ${PROJECT_PATH}
31 | #- go fmt ./... && [[ -z `git status -s` ]]
32 | - go test ./... -coverprofile=coverage.txt -covermode=atomic
33 |
34 | after_success:
35 | - bash <(curl -s https://codecov.io/bash) || echo 'Codecov failed to upload'
36 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## How to Contribute
2 | Contributions are warmly welcome! Be it trivial cleanup, major new feature or other suggestion. Read this [how to contribute](http://rocketmq.apache.org/docs/how-to-contribute/) guide for more details.
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Apache RocketMQ
2 | Copyright 2016-2024 The Apache Software Foundation
3 |
4 | This product includes software developed at
5 | The Apache Software Foundation (http://www.apache.org/).
6 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## What is the purpose of the change
2 |
3 | XXXXX
4 |
5 | ## Brief changelog
6 |
7 | XX
8 |
9 | ## Verifying this change
10 |
11 | XXXX
12 |
13 | 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`.
14 |
15 | - [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.
16 | - [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.
17 | - [x] Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
18 | - [x] Write necessary unit-test(over 80% coverage) to verify your logic correction, more mock a little better when a cross-module dependency exists.
19 | - [ ] If this contribution is large, please file an [Apache Individual Contributor License Agreement](http://www.apache.org/licenses/#clas).
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## RocketMQ Client Go
2 | [](https://app.travis-ci.com/apache/rocketmq-client-go)
3 | [](https://www.apache.org/licenses/LICENSE-2.0.html)
4 | [](https://github.com/apache/rocketmq-client-go/actions/workflows/codeql-analysis.yml)
5 | [](https://github.com/apache/rocketmq-client-go/actions/workflows/tests.yaml)
6 | [](https://codecov.io/gh/apache/rocketmq-client-go)
7 | [](https://goreportcard.com/report/github.com/apache/rocketmq-client-go)
8 | [](https://godoc.org/github.com/apache/rocketmq-client-go)
9 | [](https://github.com/apache/rocketmq-client-go/releases)
10 | [](http://isitmaintained.com/project/apache/rocketmq-client-go "Average time to resolve an issue")
11 | [](http://isitmaintained.com/project/apache/rocketmq-client-go "Percentage of issues still open")
12 | [](https://twitter.com/intent/follow?screen_name=ApacheRocketMQ)
13 |
14 | A product ready RocketMQ Client in pure go, which supports almost the full features of Apache RocketMQ, such as pub and sub messages, ACL, tracing and so on.
15 |
16 | ----------
17 | ## [Are you using RocketMQ Go SDK?](https://github.com/apache/rocketmq-client-go/issues/423)
18 | [Here](https://github.com/apache/rocketmq-client-go/issues/423), we sincerely invite you to take a minute to feedback on your usage scenario.
19 |
20 | ----------
21 | ## Features
22 | For 2.X version, it supports:
23 | * sending message in synchronous mode
24 | * sending message in asynchronous mode
25 | * sending message in oneway mode
26 | * sending message in batch mode
27 | * sending orderly messages
28 | * sending delay messages
29 | * sending transaction messages
30 | * consuming message using push model
31 | * consuming message using pull model
32 | * consuming message using broadcast model
33 | * message tracing for pub and sub messages
34 | * ACL for producers and consumers
35 | * request-reply model
36 |
37 | ----------
38 | ## How to use
39 | * Step-by-step instruction are provided in [RocketMQ Go Client Introduction](docs/Introduction.md)
40 | * Consult [RocketMQ Quick Start](https://rocketmq.apache.org/docs/quick-start/) to setup rocketmq broker and nameserver.
41 |
42 | ----------
43 | ## Apache RocketMQ Community
44 | * [RocketMQ Community Projects](https://github.com/apache/rocketmq-externals)
45 |
46 | ----------
47 | ## Contact us
48 | * Mailing Lists:
49 | * Home:
50 | * Docs:
51 | * Issues:
52 | * Ask:
53 | * Slack:
54 |
55 | ----------
56 | ## How to Contribute
57 | Contributions are warmly welcome! Be it trivial cleanup, major new feature or other suggestion. Read this [how to contribute](http://rocketmq.apache.org/docs/how-to-contribute/) guide for more details.
58 |
59 |
60 | ----------
61 | ## License
62 | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) Copyright (C) Apache Software Foundation
63 |
--------------------------------------------------------------------------------
/admin/option.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package admin
19 |
20 | func defaultTopicConfigCreate() TopicConfigCreate {
21 | opts := TopicConfigCreate{
22 | DefaultTopic: "defaultTopic",
23 | ReadQueueNums: 8,
24 | WriteQueueNums: 8,
25 | Perm: 6,
26 | TopicFilterType: "SINGLE_TAG",
27 | TopicSysFlag: 0,
28 | Order: false,
29 | }
30 | return opts
31 | }
32 |
33 | type TopicConfigCreate struct {
34 | Topic string
35 | BrokerAddr string
36 | DefaultTopic string
37 | ReadQueueNums int
38 | WriteQueueNums int
39 | Perm int
40 | TopicFilterType string
41 | TopicSysFlag int
42 | Order bool
43 | }
44 |
45 | type OptionCreate func(*TopicConfigCreate)
46 |
47 | func WithTopicCreate(Topic string) OptionCreate {
48 | return func(opts *TopicConfigCreate) {
49 | opts.Topic = Topic
50 | }
51 | }
52 |
53 | func WithBrokerAddrCreate(BrokerAddr string) OptionCreate {
54 | return func(opts *TopicConfigCreate) {
55 | opts.BrokerAddr = BrokerAddr
56 | }
57 | }
58 |
59 | func WithReadQueueNums(ReadQueueNums int) OptionCreate {
60 | return func(opts *TopicConfigCreate) {
61 | opts.ReadQueueNums = ReadQueueNums
62 | }
63 | }
64 |
65 | func WithWriteQueueNums(WriteQueueNums int) OptionCreate {
66 | return func(opts *TopicConfigCreate) {
67 | opts.WriteQueueNums = WriteQueueNums
68 | }
69 | }
70 |
71 | func WithPerm(Perm int) OptionCreate {
72 | return func(opts *TopicConfigCreate) {
73 | opts.Perm = Perm
74 | }
75 | }
76 |
77 | func WithTopicFilterType(TopicFilterType string) OptionCreate {
78 | return func(opts *TopicConfigCreate) {
79 | opts.TopicFilterType = TopicFilterType
80 | }
81 | }
82 |
83 | func WithTopicSysFlag(TopicSysFlag int) OptionCreate {
84 | return func(opts *TopicConfigCreate) {
85 | opts.TopicSysFlag = TopicSysFlag
86 | }
87 | }
88 |
89 | func WithOrder(Order bool) OptionCreate {
90 | return func(opts *TopicConfigCreate) {
91 | opts.Order = Order
92 | }
93 | }
94 |
95 | func defaultTopicConfigDelete() TopicConfigDelete {
96 | opts := TopicConfigDelete{}
97 | return opts
98 | }
99 |
100 | type TopicConfigDelete struct {
101 | Topic string
102 | ClusterName string
103 | NameSrvAddr []string
104 | BrokerAddr string
105 | }
106 |
107 | type OptionDelete func(*TopicConfigDelete)
108 |
109 | func WithTopicDelete(Topic string) OptionDelete {
110 | return func(opts *TopicConfigDelete) {
111 | opts.Topic = Topic
112 | }
113 | }
114 |
115 | func WithBrokerAddrDelete(BrokerAddr string) OptionDelete {
116 | return func(opts *TopicConfigDelete) {
117 | opts.BrokerAddr = BrokerAddr
118 | }
119 | }
120 |
121 | func WithClusterName(ClusterName string) OptionDelete {
122 | return func(opts *TopicConfigDelete) {
123 | opts.ClusterName = ClusterName
124 | }
125 | }
126 |
127 | func WithNameSrvAddr(NameSrvAddr []string) OptionDelete {
128 | return func(opts *TopicConfigDelete) {
129 | opts.NameSrvAddr = NameSrvAddr
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/admin/response.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package admin
19 |
20 | import "encoding/json"
21 |
22 | type RemotingSerializable struct {
23 | }
24 |
25 | func (r *RemotingSerializable) Encode(obj interface{}) ([]byte, error) {
26 | jsonStr := r.ToJson(obj, false)
27 | if jsonStr != "" {
28 | return []byte(jsonStr), nil
29 | }
30 | return nil, nil
31 | }
32 |
33 | func (r *RemotingSerializable) ToJson(obj interface{}, prettyFormat bool) string {
34 | if prettyFormat {
35 | jsonBytes, err := json.MarshalIndent(obj, "", " ")
36 | if err != nil {
37 | return ""
38 | }
39 | return string(jsonBytes)
40 | } else {
41 | jsonBytes, err := json.Marshal(obj)
42 | if err != nil {
43 | return ""
44 | }
45 | return string(jsonBytes)
46 | }
47 | }
48 | func (r *RemotingSerializable) Decode(data []byte, classOfT interface{}) (interface{}, error) {
49 | jsonStr := string(data)
50 | return r.FromJson(jsonStr, classOfT)
51 | }
52 |
53 | func (r *RemotingSerializable) FromJson(jsonStr string, classOfT interface{}) (interface{}, error) {
54 | err := json.Unmarshal([]byte(jsonStr), classOfT)
55 | if err != nil {
56 | return nil, err
57 | }
58 | return classOfT, nil
59 | }
60 |
61 | type TopicList struct {
62 | TopicList []string
63 | BrokerAddr string
64 | RemotingSerializable
65 | }
66 |
67 | type SubscriptionGroupWrapper struct {
68 | SubscriptionGroupTable map[string]SubscriptionGroupConfig
69 | DataVersion DataVersion
70 | RemotingSerializable
71 | }
72 |
73 | type DataVersion struct {
74 | Timestamp int64
75 | Counter int32
76 | }
77 |
78 | type SubscriptionGroupConfig struct {
79 | GroupName string
80 | ConsumeEnable bool
81 | ConsumeFromMinEnable bool
82 | ConsumeBroadcastEnable bool
83 | RetryMaxTimes int
84 | RetryQueueNums int
85 | BrokerId int
86 | WhichBrokerWhenConsumeSlowly int
87 | NotifyConsumerIdsChangedEnable bool
88 | }
89 |
--------------------------------------------------------------------------------
/benchmark/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "fmt"
22 | "github.com/apache/rocketmq-client-go/v2/rlog"
23 | "os"
24 | )
25 |
26 | type command interface {
27 | usage()
28 | run(args []string)
29 | }
30 |
31 | var (
32 | cmds = map[string]command{}
33 | )
34 |
35 | func registerCommand(name string, cmd command) {
36 | if cmd == nil {
37 | panic("empty command")
38 | }
39 |
40 | _, ok := cmds[name]
41 | if ok {
42 | panic(fmt.Sprintf("%s command existed", name))
43 | }
44 |
45 | cmds[name] = cmd
46 | }
47 |
48 | func usage() {
49 | rlog.Info("Command", map[string]interface{}{
50 | "name": os.Args[0],
51 | })
52 | for _, cmd := range cmds {
53 | cmd.usage()
54 | }
55 | }
56 |
57 | // go run *.go [command name] [command args]
58 | func main() {
59 | if len(os.Args) < 2 {
60 | rlog.Error("Lack Command Name", nil)
61 | usage()
62 | return
63 | }
64 |
65 | name := os.Args[1]
66 | cmd, ok := cmds[name]
67 | if !ok {
68 | rlog.Error("Command Isn't Supported", map[string]interface{}{
69 | "command": name,
70 | })
71 | usage()
72 | return
73 | }
74 |
75 | cmd.run(os.Args[2:])
76 | }
77 |
--------------------------------------------------------------------------------
/benchmark/message.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import "strings"
21 |
22 | var (
23 | longText = ""
24 | longTextLen = 0
25 | )
26 |
27 | func init() {
28 | longText = strings.Repeat("0123456789", 100)
29 | longTextLen = len(longText)
30 | }
31 |
32 | func buildMsg(size int) string {
33 | return longText[:size]
34 | }
35 |
--------------------------------------------------------------------------------
/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 | package rocketmq
18 |
--------------------------------------------------------------------------------
/consumer/interceptor.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package consumer
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "time"
24 |
25 | "github.com/apache/rocketmq-client-go/v2/internal"
26 | "github.com/apache/rocketmq-client-go/v2/internal/utils"
27 | "github.com/apache/rocketmq-client-go/v2/primitive"
28 | )
29 |
30 | // WithTrace support rocketmq trace: https://github.com/apache/rocketmq/wiki/RIP-6-Message-Trace.
31 | func WithTrace(traceCfg *primitive.TraceConfig) Option {
32 | return func(options *consumerOptions) {
33 | dispatcher := internal.NewTraceDispatcher(traceCfg)
34 | options.TraceDispatcher = dispatcher
35 | ori := options.Interceptors
36 | options.Interceptors = make([]primitive.Interceptor, 0)
37 | options.Interceptors = append(options.Interceptors, newTraceInterceptor(dispatcher))
38 | options.Interceptors = append(options.Interceptors, ori...)
39 | }
40 | }
41 |
42 | func newTraceInterceptor(dispatcher internal.TraceDispatcher) primitive.Interceptor {
43 | if dispatcher != nil {
44 | dispatcher.Start()
45 | }
46 |
47 | return func(ctx context.Context, req, reply interface{}, next primitive.Invoker) error {
48 | if dispatcher == nil {
49 | return fmt.Errorf("GetOrNewRocketMQClient faild")
50 | }
51 | consumerCtx, exist := primitive.GetConsumerCtx(ctx)
52 | if !exist || len(consumerCtx.Msgs) == 0 {
53 | return next(ctx, req, reply)
54 | }
55 |
56 | beginT := time.Now()
57 | // before traceCtx
58 | traceCx := internal.TraceContext{
59 | RequestId: primitive.CreateUniqID(),
60 | TimeStamp: time.Now().UnixNano() / int64(time.Millisecond),
61 | TraceType: internal.SubBefore,
62 | GroupName: consumerCtx.ConsumerGroup,
63 | IsSuccess: true,
64 | }
65 | beans := make([]internal.TraceBean, 0)
66 | for _, msg := range consumerCtx.Msgs {
67 | if msg == nil {
68 | continue
69 | }
70 | regionID := msg.GetRegionID()
71 | traceOn := msg.IsTraceOn()
72 | if traceOn == "false" {
73 | continue
74 | }
75 | bean := internal.TraceBean{
76 | Topic: msg.Topic,
77 | MsgId: msg.MsgId,
78 | Tags: msg.GetTags(),
79 | Keys: msg.GetKeys(),
80 | StoreTime: msg.StoreTimestamp,
81 | BodyLength: int(msg.StoreSize),
82 | RetryTimes: int(msg.ReconsumeTimes),
83 | ClientHost: utils.LocalIP,
84 | StoreHost: utils.LocalIP,
85 | }
86 | beans = append(beans, bean)
87 | traceCx.RegionId = regionID
88 | }
89 | if len(beans) > 0 {
90 | traceCx.TraceBeans = beans
91 | traceCx.TimeStamp = time.Now().UnixNano() / int64(time.Millisecond)
92 | dispatcher.Append(traceCx)
93 | }
94 |
95 | err := next(ctx, req, reply)
96 |
97 | // after traceCtx
98 | costTime := time.Since(beginT).Nanoseconds() / int64(time.Millisecond)
99 | ctxType := consumerCtx.Properties[primitive.PropCtxType]
100 | afterCtx := internal.TraceContext{
101 | TimeStamp: time.Now().UnixNano() / int64(time.Millisecond),
102 |
103 | TraceType: internal.SubAfter,
104 | RegionId: traceCx.RegionId,
105 | GroupName: traceCx.GroupName,
106 | RequestId: traceCx.RequestId,
107 | IsSuccess: consumerCtx.Success,
108 | CostTime: costTime,
109 | TraceBeans: traceCx.TraceBeans,
110 | ContextCode: primitive.ConsumeReturnType(ctxType).Ordinal(),
111 | }
112 | dispatcher.Append(afterCtx)
113 | return err
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/consumer/limiter.go:
--------------------------------------------------------------------------------
1 | package consumer
2 |
3 | type Limiter func(topic string)
4 |
--------------------------------------------------------------------------------
/consumer/lock.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package consumer
19 |
20 | import (
21 | "sync"
22 |
23 | "github.com/apache/rocketmq-client-go/v2/primitive"
24 | )
25 |
26 | type QueueLock struct {
27 | lockTable sync.Map
28 | }
29 |
30 | func newQueueLock() *QueueLock {
31 | return &QueueLock{}
32 | }
33 |
34 | func (ql QueueLock) fetchLock(queue primitive.MessageQueue) sync.Locker {
35 | v, _ := ql.lockTable.LoadOrStore(queue, new(sync.Mutex))
36 | return v.(*sync.Mutex)
37 | }
38 |
--------------------------------------------------------------------------------
/consumer/message_util.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package consumer
19 |
20 | import (
21 | "errors"
22 | "fmt"
23 |
24 | "github.com/apache/rocketmq-client-go/v2/internal"
25 | "github.com/apache/rocketmq-client-go/v2/primitive"
26 | )
27 |
28 | // CreateReplyMessage build reply message from the request message
29 | func CreateReplyMessage(requestMessage *primitive.MessageExt, body []byte) (*primitive.Message, error) {
30 | if requestMessage == nil {
31 | return nil, errors.New("create reply message fail, requestMessage cannot be null")
32 | }
33 |
34 | cluster := requestMessage.GetProperty(primitive.PropertyCluster)
35 | replyTo := requestMessage.GetProperty(primitive.PropertyMessageReplyToClient)
36 | correlationId := requestMessage.GetProperty(primitive.PropertyCorrelationID)
37 | ttl := requestMessage.GetProperty(primitive.PropertyMessageTTL)
38 |
39 | if cluster == "" {
40 | return nil, fmt.Errorf("create reply message fail, requestMessage error, property[\"%s\"] is null", cluster)
41 | }
42 |
43 | var replayMessage primitive.Message
44 |
45 | replayMessage.UnmarshalProperties(body)
46 | replayMessage.Topic = internal.GetReplyTopic(cluster)
47 | replayMessage.WithProperty(primitive.PropertyMsgType, internal.ReplyMessageFlag)
48 | replayMessage.WithProperty(primitive.PropertyCorrelationID, correlationId)
49 | replayMessage.WithProperty(primitive.PropertyMessageReplyToClient, replyTo)
50 | replayMessage.WithProperty(primitive.PropertyMessageTTL, ttl)
51 |
52 | return &replayMessage, nil
53 | }
54 |
55 | func GetReplyToClient(msg *primitive.MessageExt) string {
56 | return msg.GetProperty(primitive.PropertyMessageReplyToClient)
57 | }
58 |
--------------------------------------------------------------------------------
/consumer/option_test.go:
--------------------------------------------------------------------------------
1 | package consumer
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func getFieldString(obj interface{}, field string) string {
10 | v := reflect.Indirect(reflect.ValueOf(obj))
11 | return v.FieldByNameFunc(func(n string) bool {
12 | return n == field
13 | }).String()
14 | }
15 |
16 | func TestWithRemotingTimeout(t *testing.T) {
17 | opt := defaultPushConsumerOptions()
18 | WithRemotingTimeout(3*time.Second, 4*time.Second, 5*time.Second)(&opt)
19 | if timeout := opt.RemotingClientConfig.ConnectionTimeout; timeout != 3*time.Second {
20 | t.Errorf("consumer option WithRemotingTimeout connectionTimeout. want:%s, got=%s", 3*time.Second, timeout)
21 | }
22 | if timeout := opt.RemotingClientConfig.ReadTimeout; timeout != 4*time.Second {
23 | t.Errorf("consumer option WithRemotingTimeout readTimeout. want:%s, got=%s", 4*time.Second, timeout)
24 | }
25 | if timeout := opt.RemotingClientConfig.WriteTimeout; timeout != 5*time.Second {
26 | t.Errorf("consumer option WithRemotingTimeout writeTimeout. want:%s, got=%s", 5*time.Second, timeout)
27 | }
28 | }
29 |
30 | func TestWithUnitName(t *testing.T) {
31 | opt := defaultPushConsumerOptions()
32 | unitName := "unsh"
33 | WithUnitName(unitName)(&opt)
34 | if opt.UnitName != unitName {
35 | t.Errorf("consumer option WithUnitName. want:%s, got=%s", unitName, opt.UnitName)
36 | }
37 | }
38 |
39 | func TestWithNameServerDomain(t *testing.T) {
40 | opt := defaultPushConsumerOptions()
41 | nameServerAddr := "http://127.0.0.1:8080/nameserver/addr"
42 | WithNameServerDomain(nameServerAddr)(&opt)
43 | domainStr := getFieldString(opt.Resolver, "domain")
44 | if domainStr != nameServerAddr {
45 | t.Errorf("consumer option WithUnitName. want:%s, got=%s", nameServerAddr, domainStr)
46 | }
47 | }
48 |
49 | func TestWithNameServerDomainAndUnitName(t *testing.T) {
50 | unitName := "unsh"
51 | // test with two different orders
52 | t.Run("WithNameServerDomain & WithUnitName", func(t *testing.T) {
53 | addr := "http://127.0.0.1:8080/nameserver/addr"
54 | opt := defaultPushConsumerOptions()
55 | WithNameServerDomain(addr)(&opt)
56 | WithUnitName(unitName)(&opt)
57 |
58 | domainStr := getFieldString(opt.Resolver, "domain")
59 | expectedAddr := "http://127.0.0.1:8080/nameserver/addr-unsh?nofix=1"
60 | if domainStr != expectedAddr {
61 | t.Errorf("consumer option WithNameServerDomain & WithUnitName. want:%s, got=%s", expectedAddr, domainStr)
62 | }
63 | })
64 |
65 | t.Run("WithUnitName & WithNameServerDomain", func(t *testing.T) {
66 | addr := "http://127.0.0.1:8080/nameserver/addr"
67 | opt := defaultPushConsumerOptions()
68 | WithUnitName(unitName)(&opt)
69 | WithNameServerDomain(addr)(&opt)
70 |
71 | domainStr := getFieldString(opt.Resolver, "domain")
72 | expectedAddr := "http://127.0.0.1:8080/nameserver/addr-unsh?nofix=1"
73 | if domainStr != expectedAddr {
74 | t.Errorf("consumer option WithUnitName & WithNameServerDomain. want:%s, got=%s", expectedAddr, domainStr)
75 | }
76 | })
77 |
78 | // test with two different orders - name server with query string
79 | t.Run("WithNameServerDomain & WithUnitName", func(t *testing.T) {
80 | addr := "http://127.0.0.1:8080/nameserver/addr?labels=abc"
81 | opt := defaultPushConsumerOptions()
82 | WithNameServerDomain(addr)(&opt)
83 | WithUnitName(unitName)(&opt)
84 |
85 | domainStr := getFieldString(opt.Resolver, "domain")
86 | expectedAddr := "http://127.0.0.1:8080/nameserver/addr-unsh?nofix=1&labels=abc"
87 | if domainStr != expectedAddr {
88 | t.Errorf("consumer option WithNameServerDomain & WithUnitName. want:%s, got=%s", expectedAddr, domainStr)
89 | }
90 | })
91 |
92 | t.Run("WithUnitName & WithNameServerDomain", func(t *testing.T) {
93 | addr := "http://127.0.0.1:8080/nameserver/addr?labels=abc"
94 | opt := defaultPushConsumerOptions()
95 | WithUnitName(unitName)(&opt)
96 | WithNameServerDomain(addr)(&opt)
97 |
98 | domainStr := getFieldString(opt.Resolver, "domain")
99 | expectedAddr := "http://127.0.0.1:8080/nameserver/addr-unsh?nofix=1&labels=abc"
100 | if domainStr != expectedAddr {
101 | t.Errorf("consumer option WithUnitName & WithNameServerDomain. want:%s, got=%s", expectedAddr, domainStr)
102 | }
103 | })
104 | }
105 |
--------------------------------------------------------------------------------
/consumer/pull_consumer_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package consumer
19 |
--------------------------------------------------------------------------------
/consumer/push_consumer_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package consumer
19 |
20 | import (
21 | "context"
22 | "testing"
23 |
24 | "github.com/apache/rocketmq-client-go/v2/rlog"
25 |
26 | "github.com/apache/rocketmq-client-go/v2/internal"
27 | "github.com/apache/rocketmq-client-go/v2/primitive"
28 | "github.com/golang/mock/gomock"
29 | . "github.com/smartystreets/goconvey/convey"
30 | )
31 |
32 | func mockB4Start(c *pushConsumer) {
33 | c.topicSubscribeInfoTable.Store("TopicTest", []*primitive.MessageQueue{})
34 | }
35 |
36 | func TestStart(t *testing.T) {
37 | Convey("test Start method", t, func() {
38 | c, _ := NewPushConsumer(
39 | WithGroupName("testGroup"),
40 | WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
41 | WithConsumerModel(BroadCasting),
42 | )
43 |
44 | ctrl := gomock.NewController(t)
45 | defer ctrl.Finish()
46 |
47 | client := internal.NewMockRMQClient(ctrl)
48 | c.client = client
49 |
50 | err := c.Subscribe("TopicTest", MessageSelector{}, func(ctx context.Context,
51 | msgs ...*primitive.MessageExt) (ConsumeResult, error) {
52 | rlog.Info("Subscribe Callback", map[string]interface{}{
53 | "msgs": msgs,
54 | })
55 | return ConsumeSuccess, nil
56 | })
57 |
58 | _, exists := c.subscriptionDataTable.Load("TopicTest")
59 | So(exists, ShouldBeTrue)
60 |
61 | err = c.Unsubscribe("TopicTest")
62 | So(err, ShouldBeNil)
63 | _, exists = c.subscriptionDataTable.Load("TopicTest")
64 | So(exists, ShouldBeFalse)
65 |
66 | err = c.Subscribe("TopicTest", MessageSelector{}, func(ctx context.Context,
67 | msgs ...*primitive.MessageExt) (ConsumeResult, error) {
68 | rlog.Info("Subscribe Callback", map[string]interface{}{
69 | "msgs": msgs,
70 | })
71 | return ConsumeSuccess, nil
72 | })
73 |
74 | _, exists = c.subscriptionDataTable.Load("TopicTest")
75 | So(exists, ShouldBeTrue)
76 |
77 | client.EXPECT().ClientID().Return("127.0.0.1@DEFAULT")
78 | client.EXPECT().Start().Return()
79 | client.EXPECT().RegisterConsumer(gomock.Any(), gomock.Any()).Return(nil)
80 | client.EXPECT().UpdateTopicRouteInfo().AnyTimes().Return()
81 |
82 | Convey("test topic route info not found", func() {
83 | client.EXPECT().Shutdown().Return()
84 | client.EXPECT().UnregisterConsumer(gomock.Any()).Return()
85 | err = c.Start()
86 | So(err.Error(), ShouldContainSubstring, "route info not found")
87 | })
88 |
89 | Convey("test topic route info found", func() {
90 | client.EXPECT().RebalanceImmediately().Return()
91 | client.EXPECT().CheckClientInBroker().Return()
92 | client.EXPECT().SendHeartbeatToAllBrokerWithLock().Return()
93 | mockB4Start(c)
94 | err = c.Start()
95 | So(err, ShouldBeNil)
96 | })
97 | })
98 | }
99 |
--------------------------------------------------------------------------------
/docs/Introduction.md:
--------------------------------------------------------------------------------
1 | ## How to use
2 |
3 | ### go mod
4 | ```
5 | require (
6 | github.com/apache/rocketmq-client-go/v2 v2.1.0-rc3
7 | )
8 | ```
9 |
10 | ### Set Logger
11 | Go Client define the `Logger` interface for log output, user can specify implementation of private.
12 | in default, client use `logrus`.
13 | ```
14 | rlog.SetLogger(Logger)
15 | ```
16 |
17 | ### Send message
18 | #### Interface
19 | ```
20 | Producer interface {
21 | Start() error
22 | Shutdown() error
23 | SendSync(context.Context, *primitive.Message) (*internal.SendResult, error)
24 | SendOneWay(context.Context, *primitive.Message) error
25 | }
26 | ```
27 |
28 | #### Examples
29 | - create a new `Producer` instance
30 | ```
31 | p, err := rocketmq.NewProducer(
32 | producer.WithNameServer(endPoint),
33 | //producer.WithNsResolver(primitive.NewPassthroughResolver(endPoint)),
34 | producer.WithRetry(2),
35 | producer.WithGroupName("GID_xxxxxx"),
36 | )
37 | ```
38 |
39 | - start the producer
40 | ```go
41 | err := p.Start()
42 | ```
43 |
44 | - send message with sync
45 | ```
46 | result, err := p.SendSync(context.Background(), &primitive.Message{
47 | Topic: "test",
48 | Body: []byte("Hello RocketMQ Go Client!"),
49 | })
50 |
51 | // do something with result
52 | ```
53 |
54 | - or send message with oneway
55 | ```
56 | err := p.SendOneWay(context.Background(), &primitive.Message{
57 | Topic: "test",
58 | Body: []byte("Hello RocketMQ Go Client!"),
59 | })
60 | ```
61 | Full examples: [producer](../examples/producer)
62 |
63 | ### Consume Message
64 | now only support `PushConsumer`
65 |
66 | #### Interface
67 | ```
68 | PushConsumer interface {
69 | // Start the PushConsumer for consuming message
70 | Start() error
71 |
72 | // Shutdown the PushConsumer, all offset of MessageQueue will be sync to broker before process exit
73 | Shutdown() error
74 | // Subscribe a topic for consuming
75 | Subscribe(topic string, selector consumer.MessageSelector,
76 | f func(context.Context, ...*primitive.MessageExt) (consumer.ConsumeResult, error)) error
77 | }
78 | ```
79 |
80 | #### Usage
81 | - Create a `PushConsumer` instance
82 | ```
83 | c, err := rocketmq.NewPushConsumer(
84 | consumer.WithNameServer(endPoint),
85 | consumer.WithConsumerModel(consumer.Clustering),
86 | consumer.WithGroupName("GID_XXXXXX"),
87 | )
88 | ```
89 |
90 | - Subscribe a topic(only support one topic now), and define your consuming function
91 | ```
92 | err := c.Subscribe("test", consumer.MessageSelector{}, func(ctx context.Context,
93 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
94 | rlog.Info("Subscribe Callback", map[string]interface{}{
95 | "msgs": msgs,
96 | })
97 | return consumer.ConsumeSuccess, nil
98 | })
99 | ```
100 | - start the consumer(**NOTE: MUST after subscribe**)
101 | ```
102 | err = c.Start()
103 | ```
104 |
105 | Full examples: [consumer](../examples/consumer)
106 |
107 |
108 | ### Admin: Topic Operation
109 |
110 | #### Examples
111 | - create topic
112 | ```
113 | testAdmin, err := admin.NewAdmin(admin.WithResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})))
114 | err = testAdmin.CreateTopic(
115 | context.Background(),
116 | admin.WithTopicCreate("newTopic"),
117 | admin.WithBrokerAddrCreate("127.0.0.1:10911"),
118 | )
119 | ```
120 |
121 | - delete topic
122 | `ClusterName` not supported yet
123 | ```
124 | err = testAdmin.DeleteTopic(
125 | context.Background(),
126 | admin.WithTopicDelete("newTopic"),
127 | //admin.WithBrokerAddrDelete("127.0.0.1:10911"), //optional
128 | //admin.WithNameSrvAddr(nameSrvAddr), //optional
129 | )
130 | ```
131 |
--------------------------------------------------------------------------------
/docs/feature.md:
--------------------------------------------------------------------------------
1 | # Feature
2 |
3 | ## Producer
4 |
5 | ### MessageType
6 | - [x] NormalMessage
7 | - [ ] TransactionMessage
8 | - [ ] DelayMessage
9 |
10 | ### SendWith
11 | - [x] Sync
12 | - [ ] Async
13 | - [x] OneWay
14 |
15 | ### Other
16 | - [ ] Config
17 | - [ ] MessageId Generate
18 | - [ ] CompressMsg
19 | - [ ] LoadBalance
20 | - [ ] DefaultTopic
21 | - [ ] VipChannel
22 | - [ ] Retry
23 | - [ ] Hook
24 | - [ ] CheckRequestQueue
25 | - [ ] MQFaultStrategy
26 |
27 | ## Consumer
28 |
29 | ### ReceiveType
30 | - [x] Push
31 | - [ ] Pull
32 |
33 | ### ConsumingType
34 | - [x] Concurrently
35 | - [ ] Orderly
36 |
37 | ### MessageModel
38 | - [x] CLUSTERING
39 | - [x] BROADCASTING
40 |
41 | ### AllocateMessageQueueStrategy
42 | - [x] AllocateMessageQueueAveragely
43 | - [x] AllocateMessageQueueAveragelyByCircle
44 | - [X] AllocateMessageQueueByConfig
45 | - [X] AllocateMessageQueueByMachineRoom
46 |
47 | ### Other
48 | - [x] Rebalance
49 | - [x] Flow Control
50 | - [ ] compress
51 | - [x] ConsumeFromWhere
52 | - [ ] Retry(sendMessageBack)
53 | - [ ] Hook
54 |
55 | ## Common
56 | - [ ] PollNameServer
57 | - [x] Heartbeat
58 | - [x] UpdateTopicRouteInfoFromNameServer
59 | - [ ] CleanOfflineBroker
60 | - [ ] ClearExpiredMessage(form consumer consumeMessageService)
61 |
62 | ## Remoting
63 | - [x] API
64 | - [x] InvokeSync
65 | - [x] InvokeAsync
66 | - [x] InvokeOneWay
67 | - [x] Serialize
68 | - [x] JSON
69 | - [x] ROCKETMQ
70 | - [ ] Other
71 | - [ ] VIPChannel
72 | - [ ] RPCHook
73 |
74 | ## Admin
75 |
76 | ### Topic/Cluster
77 | - [x] updateTopic
78 | - [x] deleteTopic
79 | - [ ] updateSubGroup
80 | - [ ] deleteSubGroup
81 | - [ ] updateBrokerConfig
82 | - [ ] updateTopicPerm
83 | - [ ] listTopic
84 | - [ ] topicRoute
85 | - [ ] topicStatus
86 | - [ ] topicClusterList
--------------------------------------------------------------------------------
/docs/images/client-design.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apache/rocketmq-client-go/67ec50b93040567856cdbb32c03d607d8c292c3d/docs/images/client-design.png
--------------------------------------------------------------------------------
/docs/zh/native-design_zh.md:
--------------------------------------------------------------------------------
1 | # RocketMQ Go Client Design Draft
2 |
3 | ## Architecture
4 |
5 | ### Overview
6 | 
7 |
8 | ### Description
9 | 在RocketMQ Java Client的实现里面,代码耦合了大量的admin方面的功能, 其为了尽可能的提高代码复用率,代码的依赖关系较为复杂、接口的设计比
10 | 较重、语义的界限不够清晰。因此,为了避免简单的对Java代码进行翻译,故Go客户端进行了重新的设计,剥离了admin相关的逻辑。整体如上面的图所示,在逻辑层次上,按照请求
11 | 顺序,从下到上总共分为三层:
12 | - remote层:client网络通信和私有协议层,将到每个到服务端(NameServer或Broker)的连接实体抽象为一个client结构,并在这里实现了网络数据的
13 | 序列/反序列化。
14 | - 公共层:由于remote层对上层只暴露了`Sync/Async/Oneway`三个接口,所以对于特定的Request/Response、连接的管理、路由等信息的处理和维护、
15 | 其它`producer/consumer`共用的逻辑,均在这里实现。
16 | - 业务逻辑层:在这里实现各种producer、consumer的语义。所有producer、consumer专有的逻辑,均放到这里实现,如`queue selector`,
17 | `consume balance`, `offset storage`等。除了基础的数据结构外,producer和consumer之间内部的代码不能进行复用。
18 |
19 |
20 | ## 设计目标
21 | 0. 兼容cgo版本已经暴露出去的API
22 | 1. 实现语义清晰、轻量的API接口
23 | 2. 依赖关系清晰简单,设计和实现要正交
24 |
25 | ## 目录结构
26 | ### 源码
27 | - producer:producer相关逻辑
28 | - consumer:consumer相关逻辑
29 | - common(可改名):连接管理和路由管理相关的通用逻辑
30 | - remote:网络通信和序列化
31 |
32 | ### 其它
33 | - benchmark:压力测试相关代码
34 | - core:1.2版本cgo的代码库,该目录下的代码将会被移除,只保留API进行兼容,并会被标记为`Deprecated`
35 | - docs:文档,包括面向用户和开发者
36 | - examples:示例代码
37 | - test:集成测试代码
38 |
39 | ## API
40 |
41 | ### remote
42 | ```go
43 | NewRemotingCommand(code int16, header CustomHeader) *RemotingCommand
44 |
45 | // send a request to servers and return until response received.
46 | SendMessageSync(ctx context.Context, brokerAddrs, brokerName string, request *SendMessageRequest, msgs []*Message) (*SendResult, error)
47 |
48 | SendMessageAsync(ctx context.Context, brokerAddrs, brokerName string, request *SendMessageRequest, msgs []*Message, f func(result *SendResult)) error
49 |
50 | SendMessageOneWay(ctx context.Context, brokerAddrs string, request *SendMessageRequest, msgs []*Message) (*SendResult, error)
51 | ```
52 |
53 | ### common
54 | All struct needed has been defined in codebase.
55 |
56 | ```go
57 | // PullMessage with sync
58 | SendMessage(topic string, msgs *[]Message) error
59 |
60 | // SendMessageAsync send message with batch by async
61 | SendMessageAsync(topic string, msgs *[]Message, f func(result *SendResult)) error
62 |
63 | // PullMessage with sync
64 | PullMessage(ctx context.Context, brokerAddrs string, request *PullMessageRequest) (*PullResult, error)
65 |
66 | // PullMessageAsync pull message async
67 | func PullMessageAsync(ctx context.Context, brokerAddrs string, request *PullMessageRequest, f func(result *PullResult)) error
68 |
69 | // QueryMaxOffset with specific queueId and topic
70 | QueryMaxOffset(topic string, queueId int) error
71 |
72 | // QueryConsumerOffset with specific queueId and topic of consumerGroup
73 | QueryConsumerOffset(consumerGroup, topic string, queue int) (int64, error)
74 |
75 | // SearchOffsetByTimestamp with specific queueId and topic
76 | SearchOffsetByTimestamp(topic string, queue int, timestamp int64) (int64, error)
77 |
78 | // UpdateConsumerOffset with specific queueId and topic
79 | UpdateConsumerOffset(consumerGroup, topic string, queue int, offset int64) error
80 | ```
81 |
82 | ## Road map
83 | for more details about features: [feature-list](../feature.md)
84 |
85 | ### Milestone1(due: 2019.3.10)
86 |
87 | #### producer
88 | - [ ] normal message
89 | - [ ] order message
90 |
91 | #### consumer
92 | - [ ] normal message with pull/push
93 | - [ ] order message with pull/push
94 | - [ ] rebalance
95 | - [ ] offset manager
96 |
97 | #### common
98 | - [ ] API wrapper
99 | - [ ] connections manager
100 | - [ ] route
101 |
102 | #### remote
103 | - [ ] serializer
104 | - [ ] communication
105 | - [ ] processor
106 | - [ ] RPC
107 |
108 | ### Milestone2 (2019.4.12)
109 | - Transaction Message
110 | - ACL
111 | - Message Tracing
112 |
113 | ## sub project
114 | - RocketMQ Administration tools: JVM too heavy for command line tools
--------------------------------------------------------------------------------
/errors/errors.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package errors
19 |
20 | import "errors"
21 |
22 | var (
23 | ErrRequestTimeout = errors.New("request timeout")
24 | ErrMQEmpty = errors.New("MessageQueue is nil")
25 | ErrOffset = errors.New("offset < 0")
26 | ErrNumbers = errors.New("numbers < 0")
27 | ErrEmptyTopic = errors.New("empty topic")
28 | ErrEmptyNameSrv = errors.New("empty namesrv")
29 | ErrEmptyGroupID = errors.New("empty group id")
30 | ErrTestMin = errors.New("test minutes must be positive integer")
31 | ErrOperationInterval = errors.New("operation interval must be positive integer")
32 | ErrMessageBody = errors.New("message body size must be positive integer")
33 | ErrEmptyExpression = errors.New("empty expression")
34 | ErrCreated = errors.New("consumer group has been created")
35 | ErrBrokerNotFound = errors.New("broker can not found")
36 | ErrStartTopic = errors.New("cannot subscribe topic since client either failed to start or has been shutdown.")
37 | ErrSubscriptionType = errors.New("subscribe type is not matched")
38 | ErrBlankSubType = errors.New("subscribe type should not be blank")
39 | ErrResponse = errors.New("response error")
40 | ErrCompressLevel = errors.New("unsupported compress level")
41 | ErrUnknownIP = errors.New("unknown IP address")
42 | ErrService = errors.New("service close is not running, please check")
43 | ErrTopicNotExist = errors.New("topic not exist")
44 | ErrRouteNotFound = errors.New("topic route not found")
45 | ErrNotExisted = errors.New("not existed")
46 | ErrNoNameserver = errors.New("nameServerAddrs can't be empty.")
47 | ErrMultiIP = errors.New("multiple IP addr does not support")
48 | ErrIllegalIP = errors.New("IP addr error")
49 | ErrTopicEmpty = errors.New("topic is nil")
50 | ErrMessageEmpty = errors.New("message is nil")
51 | ErrNotRunning = errors.New("producer not started")
52 | ErrPullConsumer = errors.New("pull consumer has not supported")
53 | ErrProducerCreated = errors.New("producer group has been created")
54 | ErrMultipleTopics = errors.New("the topic of the messages in one batch should be the same")
55 | )
56 |
--------------------------------------------------------------------------------
/examples/admin/group/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "time"
24 |
25 | "github.com/apache/rocketmq-client-go/v2/admin"
26 | "github.com/apache/rocketmq-client-go/v2/primitive"
27 | )
28 |
29 | func main() {
30 | //clusterName := "DefaultCluster"
31 | nameSrvAddr := []string{"127.0.0.1:9876"}
32 | brokerAddr := "127.0.0.1:10911"
33 |
34 | testAdmin, err := admin.NewAdmin(
35 | admin.WithResolver(primitive.NewPassthroughResolver(nameSrvAddr)),
36 | admin.WithCredentials(primitive.Credentials{
37 | AccessKey: "RocketMQ",
38 | SecretKey: "12345678",
39 | }),
40 | )
41 |
42 | // group list
43 | result, err := testAdmin.GetAllSubscriptionGroup(context.Background(), brokerAddr, 3*time.Second)
44 | if err != nil {
45 | fmt.Println("GetAllSubscriptionGroup error:", err.Error())
46 | }
47 | fmt.Println(result.SubscriptionGroupTable)
48 |
49 | err = testAdmin.Close()
50 | if err != nil {
51 | fmt.Printf("Shutdown admin error: %s", err.Error())
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/examples/admin/topic/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 |
24 | "github.com/apache/rocketmq-client-go/v2/admin"
25 | "github.com/apache/rocketmq-client-go/v2/primitive"
26 | )
27 |
28 | func main() {
29 | topic := "newOne"
30 | //clusterName := "DefaultCluster"
31 | nameSrvAddr := []string{"127.0.0.1:9876"}
32 | brokerAddr := "127.0.0.1:10911"
33 |
34 | testAdmin, err := admin.NewAdmin(
35 | admin.WithResolver(primitive.NewPassthroughResolver(nameSrvAddr)),
36 | admin.WithCredentials(primitive.Credentials{
37 | AccessKey: "RocketMQ",
38 | SecretKey: "12345678",
39 | }),
40 | )
41 |
42 | // topic list
43 | result, err := testAdmin.FetchAllTopicList(context.Background())
44 | if err != nil {
45 | fmt.Println("FetchAllTopicList error:", err.Error())
46 | }
47 | fmt.Println(result.TopicList)
48 |
49 | //create topic
50 | err = testAdmin.CreateTopic(
51 | context.Background(),
52 | admin.WithTopicCreate(topic),
53 | admin.WithBrokerAddrCreate(brokerAddr),
54 | )
55 | if err != nil {
56 | fmt.Println("Create topic error:", err.Error())
57 | }
58 |
59 | //deletetopic
60 | err = testAdmin.DeleteTopic(
61 | context.Background(),
62 | admin.WithTopicDelete(topic),
63 | //admin.WithBrokerAddrDelete(brokerAddr),
64 | //admin.WithNameSrvAddr(nameSrvAddr),
65 | )
66 | if err != nil {
67 | fmt.Println("Delete topic error:", err.Error())
68 | }
69 |
70 | err = testAdmin.Close()
71 | if err != nil {
72 | fmt.Printf("Shutdown admin error: %s", err.Error())
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/examples/consumer/acl/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/consumer"
28 | "github.com/apache/rocketmq-client-go/v2/primitive"
29 | )
30 |
31 | func main() {
32 | c, err := rocketmq.NewPushConsumer(
33 | consumer.WithGroupName("testGroup"),
34 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | consumer.WithCredentials(primitive.Credentials{
36 | AccessKey: "RocketMQ",
37 | SecretKey: "12345678",
38 | }),
39 | )
40 | if err != nil {
41 | fmt.Println("init consumer error: " + err.Error())
42 | os.Exit(0)
43 | }
44 |
45 | err = c.Subscribe("test", consumer.MessageSelector{}, func(ctx context.Context,
46 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
47 | fmt.Printf("subscribe callback: %v \n", msgs)
48 | return consumer.ConsumeSuccess, nil
49 | })
50 | if err != nil {
51 | fmt.Println(err.Error())
52 | }
53 | // Note: start after subscribe
54 | err = c.Start()
55 | if err != nil {
56 | fmt.Println(err.Error())
57 | os.Exit(-1)
58 | }
59 | time.Sleep(time.Hour)
60 | err = c.Shutdown()
61 | if err != nil {
62 | fmt.Printf("Shutdown Consumer error: %s", err.Error())
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/examples/consumer/broadcast/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/consumer"
28 | "github.com/apache/rocketmq-client-go/v2/primitive"
29 | )
30 |
31 | func main() {
32 | c, _ := rocketmq.NewPushConsumer(
33 | consumer.WithGroupName("testGroup"),
34 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | consumer.WithConsumeFromWhere(consumer.ConsumeFromFirstOffset),
36 | consumer.WithConsumerModel(consumer.BroadCasting),
37 | )
38 | err := c.Subscribe("min", consumer.MessageSelector{}, func(ctx context.Context,
39 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
40 | fmt.Printf("subscribe callback: %v \n", msgs)
41 | return consumer.ConsumeSuccess, nil
42 | })
43 | if err != nil {
44 | fmt.Println(err.Error())
45 | }
46 | // Note: start after subscribe
47 | err = c.Start()
48 | if err != nil {
49 | fmt.Println(err.Error())
50 | os.Exit(-1)
51 | }
52 | time.Sleep(time.Hour)
53 | err = c.Shutdown()
54 | if err != nil {
55 | fmt.Printf("Shutdown Consumer error: %s", err.Error())
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/examples/consumer/delay/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/consumer"
28 | "github.com/apache/rocketmq-client-go/v2/primitive"
29 | )
30 |
31 | func main() {
32 | c, _ := rocketmq.NewPushConsumer(
33 | consumer.WithGroupName("testGroup"),
34 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | )
36 | err := c.Subscribe("TopicTest", consumer.MessageSelector{}, func(ctx context.Context,
37 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
38 |
39 | for _, msg := range msgs {
40 | t := time.Now().UnixNano()/int64(time.Millisecond) - msg.BornTimestamp
41 | fmt.Printf("Receive message[msgId=%s] %d ms later\n", msg.MsgId, t)
42 | }
43 |
44 | return consumer.ConsumeSuccess, nil
45 | })
46 | if err != nil {
47 | fmt.Println(err.Error())
48 | }
49 | // Note: start after subscribe
50 | err = c.Start()
51 | if err != nil {
52 | fmt.Println(err.Error())
53 | os.Exit(-1)
54 | }
55 | time.Sleep(time.Hour)
56 | err = c.Shutdown()
57 | if err != nil {
58 | fmt.Printf("Shutdown Consumer error: %s", err.Error())
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/examples/consumer/interceptor/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/consumer"
28 | "github.com/apache/rocketmq-client-go/v2/primitive"
29 | )
30 |
31 | func main() {
32 | c, _ := rocketmq.NewPushConsumer(
33 | consumer.WithGroupName("testGroup"),
34 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | consumer.WithConsumerModel(consumer.Clustering),
36 | consumer.WithConsumeFromWhere(consumer.ConsumeFromFirstOffset),
37 | consumer.WithInterceptor(UserFistInterceptor(), UserSecondInterceptor()))
38 | err := c.Subscribe("TopicTest", consumer.MessageSelector{}, func(ctx context.Context,
39 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
40 | fmt.Printf("subscribe callback: %v \n", msgs)
41 | return consumer.ConsumeSuccess, nil
42 | })
43 | if err != nil {
44 | fmt.Println(err.Error())
45 | }
46 | // Note: start after subscribe
47 | err = c.Start()
48 | if err != nil {
49 | fmt.Println(err.Error())
50 | os.Exit(-1)
51 | }
52 | time.Sleep(time.Hour)
53 | err = c.Shutdown()
54 | if err != nil {
55 | fmt.Printf("Shutdown Consumer error: %s", err.Error())
56 | }
57 | }
58 |
59 | func UserFistInterceptor() primitive.Interceptor {
60 | return func(ctx context.Context, req, reply interface{}, next primitive.Invoker) error {
61 | msgCtx, _ := primitive.GetConsumerCtx(ctx)
62 | fmt.Printf("msgCtx: %v, mehtod: %s", msgCtx, primitive.GetMethod(ctx))
63 |
64 | msgs := req.([]*primitive.MessageExt)
65 | fmt.Printf("user first interceptor before invoke: %v\n", msgs)
66 | e := next(ctx, msgs, reply)
67 |
68 | holder := reply.(*consumer.ConsumeResultHolder)
69 | fmt.Printf("user first interceptor after invoke: %v, result: %v\n", msgs, holder)
70 | return e
71 | }
72 | }
73 |
74 | func UserSecondInterceptor() primitive.Interceptor {
75 | return func(ctx context.Context, req, reply interface{}, next primitive.Invoker) error {
76 | msgs := req.([]*primitive.MessageExt)
77 | fmt.Printf("user second interceptor before invoke: %v\n", msgs)
78 | e := next(ctx, msgs, reply)
79 | holder := reply.(*consumer.ConsumeResultHolder)
80 | fmt.Printf("user second interceptor after invoke: %v, result: %v\n", msgs, holder)
81 | return e
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/examples/consumer/namespace/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/consumer"
28 | "github.com/apache/rocketmq-client-go/v2/primitive"
29 | )
30 |
31 | func main() {
32 | c, err := rocketmq.NewPushConsumer(
33 | consumer.WithGroupName("testGroup"),
34 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | consumer.WithCredentials(primitive.Credentials{
36 | AccessKey: "RocketMQ",
37 | SecretKey: "12345678",
38 | }),
39 | consumer.WithNamespace("namespace"),
40 | )
41 | if err != nil {
42 | fmt.Println("init consumer error: " + err.Error())
43 | os.Exit(0)
44 | }
45 |
46 | err = c.Subscribe("test", consumer.MessageSelector{}, func(ctx context.Context,
47 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
48 | fmt.Printf("subscribe callback: %v \n", msgs)
49 | return consumer.ConsumeSuccess, nil
50 | })
51 | if err != nil {
52 | fmt.Println(err.Error())
53 | }
54 | // Note: start after subscribe
55 | err = c.Start()
56 | if err != nil {
57 | fmt.Println(err.Error())
58 | os.Exit(-1)
59 | }
60 | time.Sleep(time.Hour)
61 | err = c.Shutdown()
62 | if err != nil {
63 | fmt.Printf("Shutdown Consumer error: %s", err.Error())
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/examples/consumer/orderly/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/consumer"
28 | "github.com/apache/rocketmq-client-go/v2/primitive"
29 | )
30 |
31 | func main() {
32 | c, _ := rocketmq.NewPushConsumer(
33 | consumer.WithGroupName("testGroup"),
34 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | consumer.WithConsumerModel(consumer.Clustering),
36 | consumer.WithConsumeFromWhere(consumer.ConsumeFromFirstOffset),
37 | consumer.WithConsumerOrder(true),
38 | )
39 | err := c.Subscribe("TopicTest", consumer.MessageSelector{}, func(ctx context.Context,
40 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
41 | orderlyCtx, _ := primitive.GetOrderlyCtx(ctx)
42 | fmt.Printf("orderly context: %v\n", orderlyCtx)
43 | fmt.Printf("subscribe orderly callback: %v \n", msgs)
44 | return consumer.ConsumeSuccess, nil
45 | })
46 | if err != nil {
47 | fmt.Println(err.Error())
48 | }
49 | // Note: start after subscribe
50 | err = c.Start()
51 | if err != nil {
52 | fmt.Println(err.Error())
53 | os.Exit(-1)
54 | }
55 | time.Sleep(time.Hour)
56 | err = c.Shutdown()
57 | if err != nil {
58 | fmt.Printf("Shutdown Consumer error: %s", err.Error())
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/examples/consumer/pull/poll/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "log"
23 | "net/http"
24 | _ "net/http/pprof"
25 | "time"
26 |
27 | "github.com/apache/rocketmq-client-go/v2"
28 |
29 | "github.com/apache/rocketmq-client-go/v2/rlog"
30 |
31 | "github.com/apache/rocketmq-client-go/v2/consumer"
32 | "github.com/apache/rocketmq-client-go/v2/primitive"
33 | )
34 |
35 | const (
36 | nameSrvAddr = "http://127.0.0.1:9876"
37 | accessKey = "rocketmq"
38 | secretKey = "12345678"
39 | topic = "test-topic"
40 | consumerGroupName = "testPullGroup"
41 | tag = "testPull"
42 | namespace = "ns"
43 | )
44 |
45 | var pullConsumer rocketmq.PullConsumer
46 | var sleepTime = 1 * time.Second
47 |
48 | func main() {
49 | go func() {
50 | log.Println(http.ListenAndServe("localhost:6060", nil))
51 | }()
52 | rlog.SetLogLevel("info")
53 | var nameSrv, err = primitive.NewNamesrvAddr(nameSrvAddr)
54 | if err != nil {
55 | log.Fatalf("NewNamesrvAddr err: %v", err)
56 | }
57 | pullConsumer, err = rocketmq.NewPullConsumer(
58 | consumer.WithGroupName(consumerGroupName),
59 | consumer.WithNameServer(nameSrv),
60 | consumer.WithCredentials(primitive.Credentials{
61 | AccessKey: accessKey,
62 | SecretKey: secretKey,
63 | }),
64 | consumer.WithNamespace(namespace),
65 | consumer.WithMaxReconsumeTimes(2),
66 | )
67 | if err != nil {
68 | log.Fatalf("fail to new pullConsumer: %v", err)
69 | }
70 | selector := consumer.MessageSelector{
71 | Type: consumer.TAG,
72 | Expression: tag,
73 | }
74 | err = pullConsumer.Subscribe(topic, selector)
75 | if err != nil {
76 | log.Fatalf("fail to Subscribe: %v", err)
77 | }
78 | err = pullConsumer.Start()
79 | if err != nil {
80 | log.Fatalf("fail to Start: %v", err)
81 | }
82 |
83 | for {
84 | poll()
85 | }
86 | }
87 |
88 | func poll() {
89 | cr, err := pullConsumer.Poll(context.TODO(), time.Second*5)
90 | if consumer.IsNoNewMsgError(err) {
91 | return
92 | }
93 | if err != nil {
94 | log.Printf("[poll error] err=%v", err)
95 | time.Sleep(sleepTime)
96 | return
97 | }
98 | // todo LOGIC CODE HERE
99 | log.Println("msgList: ", cr.GetMsgList())
100 | log.Println("messageQueue: ", cr.GetMQ())
101 | log.Println("processQueue: ", cr.GetPQ())
102 | // pullConsumer.ACK(context.TODO(), cr, consumer.ConsumeRetryLater)
103 | pullConsumer.ACK(context.TODO(), cr, consumer.ConsumeSuccess)
104 | }
105 |
--------------------------------------------------------------------------------
/examples/consumer/pull/poll_assign/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "log"
23 | _ "net/http/pprof"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 |
28 | "github.com/apache/rocketmq-client-go/v2/rlog"
29 |
30 | "github.com/apache/rocketmq-client-go/v2/consumer"
31 | "github.com/apache/rocketmq-client-go/v2/primitive"
32 | )
33 |
34 | const (
35 | nameSrvAddr = "http://127.0.0.1:9876"
36 | accessKey = "rocketmq"
37 | secretKey = "12345678"
38 | topic = "test-topic"
39 | consumerGroupName = "testPullGroup"
40 | tag = "testPull"
41 | namespace = "ns"
42 | )
43 |
44 | var pullConsumer rocketmq.PullConsumer
45 | var sleepTime = 1 * time.Second
46 |
47 | func main() {
48 | rlog.SetLogLevel("info")
49 | var nameSrv, err = primitive.NewNamesrvAddr(nameSrvAddr)
50 | if err != nil {
51 | log.Fatalf("NewNamesrvAddr err: %v", err)
52 | }
53 | pullConsumer, err = rocketmq.NewPullConsumer(
54 | consumer.WithGroupName(consumerGroupName),
55 | consumer.WithNameServer(nameSrv),
56 | consumer.WithNamespace(namespace),
57 | consumer.WithMaxReconsumeTimes(2),
58 | )
59 | if err != nil {
60 | log.Fatalf("fail to new pullConsumer: %v", err)
61 | }
62 |
63 | // assign nil firstly to help consumer start up
64 | err = pullConsumer.Assign(topic, nil)
65 | if err != nil {
66 | log.Fatalf("fail to Assign: %v", err)
67 | }
68 | err = pullConsumer.Start()
69 | if err != nil {
70 | log.Fatalf("fail to Start: %v", err)
71 | }
72 |
73 | mqs, err := pullConsumer.GetTopicRouteInfo(topic)
74 | if err != nil {
75 | log.Fatalf("fail to GetTopicRouteInfo: %v", err)
76 | }
77 |
78 | for _, mq := range mqs {
79 | offset, err := pullConsumer.OffsetForTimestamp(mq, time.Now().UnixMilli()-60*10)
80 | if err != nil {
81 | log.Fatalf("fail to get offset for timestamp: %v", err)
82 | } else {
83 | pullConsumer.SeekOffset(mq, offset)
84 | }
85 | }
86 |
87 | err = pullConsumer.Assign(topic, mqs)
88 | if err != nil {
89 | log.Fatalf("fail to Assign: %v", err)
90 | }
91 |
92 | for {
93 | poll()
94 | }
95 | }
96 |
97 | func poll() {
98 | cr, err := pullConsumer.Poll(context.TODO(), time.Second*5)
99 | if consumer.IsNoNewMsgError(err) {
100 | log.Println("no new msg")
101 | return
102 | }
103 | if err != nil {
104 | log.Printf("[poll error] err=%v", err)
105 | time.Sleep(sleepTime)
106 | return
107 | }
108 |
109 | // todo LOGIC CODE HERE
110 | log.Println("msgList: ", cr.GetMsgList())
111 | log.Println("messageQueue: ", cr.GetMQ())
112 | log.Println("processQueue: ", cr.GetPQ())
113 | // pullConsumer.ACK(context.TODO(), cr, consumer.ConsumeRetryLater)
114 | pullConsumer.ACK(context.TODO(), cr, consumer.ConsumeSuccess)
115 | }
116 |
--------------------------------------------------------------------------------
/examples/consumer/pull/pull/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "log"
23 | "time"
24 |
25 | "github.com/apache/rocketmq-client-go/v2"
26 |
27 | "github.com/apache/rocketmq-client-go/v2/rlog"
28 |
29 | "github.com/apache/rocketmq-client-go/v2/consumer"
30 | "github.com/apache/rocketmq-client-go/v2/primitive"
31 | )
32 |
33 | const (
34 | nameSrvAddr = "http://127.0.0.1:9876"
35 | accessKey = "rocketmq"
36 | secretKey = "12345678"
37 | topic = "test-topic"
38 | consumerGroupName = "testPullGroup"
39 | tag = "testPull"
40 | namespace = "ns"
41 | )
42 |
43 | var pullConsumer rocketmq.PullConsumer
44 | var sleepTime = 1 * time.Second
45 |
46 | const refreshPersistOffsetDuration = time.Second * 5
47 |
48 | func main() {
49 | rlog.SetLogLevel("info")
50 | var nameSrv, err = primitive.NewNamesrvAddr(nameSrvAddr)
51 | if err != nil {
52 | log.Fatalf("NewNamesrvAddr err: %v", err)
53 | }
54 | pullConsumer, err = rocketmq.NewPullConsumer(
55 | consumer.WithGroupName(consumerGroupName),
56 | consumer.WithNameServer(nameSrv),
57 | consumer.WithCredentials(primitive.Credentials{
58 | AccessKey: accessKey,
59 | SecretKey: secretKey,
60 | }),
61 | consumer.WithNamespace(namespace),
62 | consumer.WithMaxReconsumeTimes(2),
63 | )
64 | if err != nil {
65 | log.Fatalf("fail to new pullConsumer: %v", err)
66 | }
67 | selector := consumer.MessageSelector{
68 | Type: consumer.TAG,
69 | Expression: tag,
70 | }
71 | err = pullConsumer.Subscribe(topic, selector)
72 | if err != nil {
73 | log.Fatalf("fail to Subscribe: %v", err)
74 | }
75 | err = pullConsumer.Start()
76 | if err != nil {
77 | log.Fatalf("fail to Start: %v", err)
78 | }
79 |
80 | timer := time.NewTimer(refreshPersistOffsetDuration)
81 | go func() {
82 | for ; true; <-timer.C {
83 | err = pullConsumer.PersistOffset(context.TODO(), topic)
84 | if err != nil {
85 | log.Printf("[pullConsumer.PersistOffset] err=%v", err)
86 | }
87 | timer.Reset(refreshPersistOffsetDuration)
88 | }
89 | }()
90 |
91 | for i := 0; i <= 4; i++ {
92 | go func() {
93 | for {
94 | pull()
95 | }
96 | }()
97 | }
98 | // make current thread hold to see pull result. TODO should update here
99 | time.Sleep(10000 * time.Second)
100 | }
101 |
102 | func pull() {
103 | resp, err := pullConsumer.Pull(context.TODO(), 1)
104 | if err != nil {
105 | log.Printf("[pull error] err=%v", err)
106 | time.Sleep(sleepTime)
107 | return
108 | }
109 | switch resp.Status {
110 | case primitive.PullFound:
111 | log.Printf("[pull message successfully] MinOffset:%d, MaxOffset:%d, nextOffset: %d, len:%d\n", resp.MinOffset, resp.MaxOffset, resp.NextBeginOffset, len(resp.GetMessages()))
112 | var queue *primitive.MessageQueue
113 | if len(resp.GetMessages()) <= 0 {
114 | return
115 | }
116 | for _, msg := range resp.GetMessageExts() {
117 | // todo LOGIC CODE HERE
118 | queue = msg.Queue
119 | //log.Println(msg.Queue, msg.QueueOffset, msg.GetKeys(), msg.MsgId, string(msg.Body))
120 | log.Println(msg)
121 | }
122 | // update offset
123 | err = pullConsumer.UpdateOffset(queue, resp.NextBeginOffset)
124 | if err != nil {
125 | log.Printf("[pullConsumer.UpdateOffset] err=%v", err)
126 | }
127 |
128 | case primitive.PullNoNewMsg, primitive.PullNoMsgMatched:
129 | log.Printf("[no pull message] next = %d\n", resp.NextBeginOffset)
130 | time.Sleep(sleepTime)
131 | return
132 | case primitive.PullBrokerTimeout:
133 | log.Printf("[pull broker timeout] next = %d\n", resp.NextBeginOffset)
134 |
135 | time.Sleep(sleepTime)
136 | return
137 | case primitive.PullOffsetIllegal:
138 | log.Printf("[pull offset illegal] next = %d\n", resp.NextBeginOffset)
139 | return
140 | default:
141 | log.Printf("[pull error] next = %d\n", resp.NextBeginOffset)
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/examples/consumer/retry/concurrent/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/consumer"
28 | "github.com/apache/rocketmq-client-go/v2/primitive"
29 | )
30 |
31 | // use concurrent consumer model, when Subscribe function return consumer.ConsumeRetryLater, the message will be
32 | // send to RocketMQ retry topic. we could set DelayLevelWhenNextConsume in ConsumeConcurrentlyContext, which used to
33 | // indicate the delay of message re-send to origin topic from retry topic.
34 | //
35 | // in this example, we always set DelayLevelWhenNextConsume=1, means that the message will be sent to origin topic after
36 | // 1s. in case of the unlimited retry, we will return consumer.ConsumeSuccess after ReconsumeTimes > 5
37 | func main() {
38 | c, _ := rocketmq.NewPushConsumer(
39 | consumer.WithGroupName("testGroup"),
40 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
41 | consumer.WithConsumerModel(consumer.Clustering),
42 | )
43 |
44 | // The DelayLevel specify the waiting time that before next reconsume,
45 | // and it range is from 1 to 18 now.
46 | //
47 | // The time of each level is the value of indexing of {level-1} in [1s, 5s, 10s, 30s,
48 | // 1m, 2m, 3m, 4m, 5m, 6m, 7m, 8m, 9m, 10m, 20m, 30m, 1h, 2h]
49 | delayLevel := 1
50 | err := c.Subscribe("TopicTest", consumer.MessageSelector{}, func(ctx context.Context,
51 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
52 | fmt.Printf("subscribe callback len: %d \n", len(msgs))
53 |
54 | concurrentCtx, _ := primitive.GetConcurrentlyCtx(ctx)
55 | concurrentCtx.DelayLevelWhenNextConsume = delayLevel // only run when return consumer.ConsumeRetryLater
56 |
57 | for _, msg := range msgs {
58 | if msg.ReconsumeTimes > 5 {
59 | fmt.Printf("msg ReconsumeTimes > 5. msg: %v", msg)
60 | return consumer.ConsumeSuccess, nil
61 | } else {
62 | fmt.Printf("subscribe callback: %v \n", msg)
63 | }
64 | }
65 | return consumer.ConsumeRetryLater, nil
66 | })
67 | if err != nil {
68 | fmt.Println(err.Error())
69 | }
70 | // Note: start after subscribe
71 | err = c.Start()
72 | if err != nil {
73 | fmt.Println(err.Error())
74 | os.Exit(-1)
75 | }
76 | time.Sleep(time.Hour)
77 | err = c.Shutdown()
78 | if err != nil {
79 | fmt.Printf("shundown Consumer error: %s", err.Error())
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/examples/consumer/retry/order/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | /**
19 | * use orderly consumer model, when Subscribe function return consumer.SuspendCurrentQueueAMoment, it will be re-send to
20 | * local msg queue for later consume if msg.ReconsumeTimes < MaxReconsumeTimes, otherwise, it will be send to rocketmq
21 | * DLQ topic, we should manually resolve the msg.
22 | */
23 | package main
24 |
25 | import (
26 | "context"
27 | "fmt"
28 | "os"
29 | "time"
30 |
31 | "github.com/apache/rocketmq-client-go/v2"
32 | "github.com/apache/rocketmq-client-go/v2/consumer"
33 | "github.com/apache/rocketmq-client-go/v2/primitive"
34 | )
35 |
36 | func main() {
37 | c, _ := rocketmq.NewPushConsumer(
38 | consumer.WithGroupName("testGroup"),
39 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
40 | consumer.WithConsumerModel(consumer.Clustering),
41 | consumer.WithConsumeFromWhere(consumer.ConsumeFromFirstOffset),
42 | consumer.WithConsumerOrder(true),
43 | consumer.WithMaxReconsumeTimes(5),
44 | )
45 |
46 | err := c.Subscribe("TopicTest", consumer.MessageSelector{}, func(ctx context.Context,
47 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
48 | orderlyCtx, _ := primitive.GetOrderlyCtx(ctx)
49 | fmt.Printf("orderly context: %v\n", orderlyCtx)
50 | fmt.Printf("subscribe orderly callback len: %d \n", len(msgs))
51 |
52 | for _, msg := range msgs {
53 | if msg.ReconsumeTimes > 5 {
54 | fmt.Printf("msg ReconsumeTimes > 5. msg: %v", msg)
55 | } else {
56 | fmt.Printf("subscribe orderly callback: %v \n", msg)
57 | }
58 | }
59 | return consumer.SuspendCurrentQueueAMoment, nil
60 |
61 | })
62 | if err != nil {
63 | fmt.Println(err.Error())
64 | }
65 | // Note: start after subscribe
66 | err = c.Start()
67 | if err != nil {
68 | fmt.Println(err.Error())
69 | os.Exit(-1)
70 | }
71 | time.Sleep(time.Hour)
72 | err = c.Shutdown()
73 | if err != nil {
74 | fmt.Printf("shundown Consumer error: %s", err.Error())
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/examples/consumer/rpc/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "time"
24 |
25 | "github.com/apache/rocketmq-client-go/v2/consumer"
26 | "github.com/apache/rocketmq-client-go/v2/primitive"
27 | "github.com/apache/rocketmq-client-go/v2/producer"
28 | )
29 |
30 | const (
31 | producerGroup = "please_rename_unique_group_name"
32 | consumerGroup = "please_rename_unique_group_name"
33 | topic = "RequestTopic"
34 | )
35 |
36 | func main() {
37 | // create a producer to send reply message
38 | replyProducer, err := producer.NewDefaultProducer(
39 | producer.WithGroupName(producerGroup),
40 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
41 | )
42 | if err != nil {
43 | fmt.Printf("error: %s\n", err)
44 | return
45 | }
46 | err = replyProducer.Start()
47 | if err != nil {
48 | fmt.Printf("error: %s\n", err)
49 | return
50 | }
51 |
52 | // create consumer
53 | c, err := consumer.NewPushConsumer(
54 | consumer.WithGroupName(consumerGroup),
55 | consumer.WithConsumeFromWhere(consumer.ConsumeFromLastOffset),
56 | consumer.WithPullInterval(0),
57 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
58 | )
59 |
60 | err = c.Subscribe(topic, consumer.MessageSelector{
61 | Type: consumer.TAG, Expression: "*"}, func(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
62 |
63 | fmt.Printf("subscribe callback: %v \n", msgs)
64 | for _, msg := range msgs {
65 | fmt.Printf("handle message: %s", msg.String())
66 | // why sleep here ? because if not sleep, consumer reply message, but producer not register to broker, it will
67 | // make the broker can't find the reply channel.
68 | // only the go client has this problem.
69 | time.Sleep(time.Millisecond * 1000)
70 | fmt.Println("consumer sleep over, start reply")
71 |
72 | replyContent := []byte("reply message contents.")
73 | replyMessage, err := consumer.CreateReplyMessage(msg, replyContent)
74 | if err != nil {
75 | fmt.Printf("create reply message err:%v\n", err)
76 | continue
77 | }
78 |
79 | replyTo := consumer.GetReplyToClient(msg)
80 | replyResult, err := replyProducer.SendSync(context.Background(), replyMessage)
81 | if err != nil {
82 | fmt.Printf("send message error: %s\n", err)
83 | continue
84 | }
85 | fmt.Printf("reply to %s , %s \n", replyTo, replyResult.String())
86 | }
87 | return consumer.ConsumeSuccess, nil
88 | })
89 | if err != nil {
90 | fmt.Printf("error: %s\n", err)
91 | return
92 | }
93 | err = c.Start()
94 | if err != nil {
95 | fmt.Printf("error: %s\n", err)
96 | return
97 | }
98 | fmt.Printf("Consumer Started.\n")
99 |
100 | time.Sleep(time.Hour)
101 | err = c.Shutdown()
102 | if err != nil {
103 | fmt.Printf("shutdown Consumer error: %s", err.Error())
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/examples/consumer/simple/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "github.com/apache/rocketmq-client-go/v2"
24 | "github.com/apache/rocketmq-client-go/v2/consumer"
25 | "github.com/apache/rocketmq-client-go/v2/primitive"
26 | "os"
27 | )
28 |
29 | func main() {
30 | sig := make(chan os.Signal)
31 | c, _ := rocketmq.NewPushConsumer(
32 | consumer.WithGroupName("testGroup"),
33 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
34 | )
35 | err := c.Subscribe("test", consumer.MessageSelector{}, func(ctx context.Context,
36 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
37 | for i := range msgs {
38 | fmt.Printf("subscribe callback: %v \n", msgs[i])
39 | }
40 |
41 | return consumer.ConsumeSuccess, nil
42 | })
43 | if err != nil {
44 | fmt.Println(err.Error())
45 | }
46 | // Note: start after subscribe
47 | err = c.Start()
48 | if err != nil {
49 | fmt.Println(err.Error())
50 | os.Exit(-1)
51 | }
52 | <-sig
53 | err = c.Shutdown()
54 | if err != nil {
55 | fmt.Printf("shutdown Consumer error: %s", err.Error())
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/examples/consumer/strategy/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/consumer"
28 | "github.com/apache/rocketmq-client-go/v2/primitive"
29 | )
30 |
31 | func main() {
32 | c, _ := rocketmq.NewPushConsumer(
33 | consumer.WithGroupName("testGroup"),
34 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | consumer.WithStrategy(consumer.AllocateByAveragely),
36 | )
37 | err := c.Subscribe("TopicTest", consumer.MessageSelector{}, func(ctx context.Context,
38 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
39 | fmt.Printf("subscribe callback: %v \n", msgs)
40 | return consumer.ConsumeSuccess, nil
41 | })
42 | if err != nil {
43 | fmt.Println(err.Error())
44 | }
45 | // Note: start after subscribe
46 | err = c.Start()
47 | if err != nil {
48 | fmt.Println(err.Error())
49 | os.Exit(-1)
50 | }
51 | time.Sleep(time.Hour)
52 | err = c.Shutdown()
53 | if err != nil {
54 | fmt.Printf("shutdown Consumer error: %s", err.Error())
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/examples/consumer/tag/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/consumer"
28 | "github.com/apache/rocketmq-client-go/v2/primitive"
29 | )
30 |
31 | func main() {
32 | c, _ := rocketmq.NewPushConsumer(
33 | consumer.WithGroupName("testGroup"),
34 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | )
36 | selector := consumer.MessageSelector{
37 | Type: consumer.TAG,
38 | Expression: "TagA || TagC",
39 | }
40 | err := c.Subscribe("TopicTest", selector, func(ctx context.Context,
41 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
42 | fmt.Printf("subscribe callback: %v \n", msgs)
43 | return consumer.ConsumeSuccess, nil
44 | })
45 | if err != nil {
46 | fmt.Println(err.Error())
47 | }
48 | err = c.Start()
49 | if err != nil {
50 | fmt.Println(err.Error())
51 | os.Exit(-1)
52 | }
53 | time.Sleep(time.Hour)
54 | err = c.Shutdown()
55 | if err != nil {
56 | fmt.Printf("shutdown Consumer error: %s", err.Error())
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/examples/consumer/tls/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/consumer"
28 | "github.com/apache/rocketmq-client-go/v2/primitive"
29 | )
30 |
31 | func main() {
32 | c, _ := rocketmq.NewPushConsumer(
33 | consumer.WithGroupName("testGroup"),
34 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | consumer.WithTls(true),
36 | )
37 | err := c.Subscribe("test", consumer.MessageSelector{}, func(ctx context.Context,
38 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
39 | for i := range msgs {
40 | fmt.Printf("subscribe callback: %v \n", msgs[i])
41 | }
42 |
43 | return consumer.ConsumeSuccess, nil
44 | })
45 | if err != nil {
46 | fmt.Println(err.Error())
47 | }
48 | // Note: start after subscribe
49 | err = c.Start()
50 | if err != nil {
51 | fmt.Println(err.Error())
52 | os.Exit(-1)
53 | }
54 | time.Sleep(time.Hour)
55 | err = c.Shutdown()
56 | if err != nil {
57 | fmt.Printf("shutdown Consumer error: %s", err.Error())
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/examples/consumer/trace/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/consumer"
28 | "github.com/apache/rocketmq-client-go/v2/primitive"
29 | )
30 |
31 | func main() {
32 | namesrvs := []string{"127.0.0.1:9876"}
33 | traceCfg := &primitive.TraceConfig{
34 | Access: primitive.Local,
35 | Resolver: primitive.NewPassthroughResolver(namesrvs),
36 | }
37 |
38 | c, _ := rocketmq.NewPushConsumer(
39 | consumer.WithGroupName("testGroup"),
40 | consumer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
41 | consumer.WithTrace(traceCfg),
42 | )
43 | err := c.Subscribe("TopicTest", consumer.MessageSelector{}, func(ctx context.Context,
44 | msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
45 | fmt.Printf("subscribe callback: %v \n", msgs)
46 | return consumer.ConsumeSuccess, nil
47 | })
48 | if err != nil {
49 | fmt.Println(err.Error())
50 | }
51 | // Note: start after subscribe
52 | err = c.Start()
53 | if err != nil {
54 | fmt.Println(err.Error())
55 | os.Exit(-1)
56 |
57 | }
58 | time.Sleep(time.Hour)
59 | err = c.Shutdown()
60 | if err != nil {
61 | fmt.Printf("shutdown Consumer error: %s", err.Error())
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/examples/producer/acl/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | // Package main implements a producer with user custom interceptor.
19 | package main
20 |
21 | import (
22 | "context"
23 | "fmt"
24 | "os"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/primitive"
28 | "github.com/apache/rocketmq-client-go/v2/producer"
29 | )
30 |
31 | func main() {
32 | p, err := rocketmq.NewProducer(
33 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
34 | producer.WithRetry(2),
35 | producer.WithCredentials(primitive.Credentials{
36 | AccessKey: "RocketMQ",
37 | SecretKey: "12345678",
38 | }),
39 | )
40 |
41 | if err != nil {
42 | fmt.Println("init producer error: " + err.Error())
43 | os.Exit(0)
44 | }
45 |
46 | err = p.Start()
47 | if err != nil {
48 | fmt.Printf("start producer error: %s", err.Error())
49 | os.Exit(1)
50 | }
51 | for i := 0; i < 100000; i++ {
52 | res, err := p.SendSync(context.Background(), primitive.NewMessage("test",
53 | []byte("Hello RocketMQ Go Client!")))
54 |
55 | if err != nil {
56 | fmt.Printf("send message error: %s\n", err)
57 | } else {
58 | fmt.Printf("send message success: result=%s\n", res.String())
59 | }
60 | }
61 | err = p.Shutdown()
62 | if err != nil {
63 | fmt.Printf("shutdown producer error: %s", err.Error())
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/examples/producer/async/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "sync"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/primitive"
28 | "github.com/apache/rocketmq-client-go/v2/producer"
29 | )
30 |
31 | // Package main implements a async producer to send message.
32 | func main() {
33 | p, _ := rocketmq.NewProducer(
34 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | producer.WithRetry(2))
36 |
37 | err := p.Start()
38 | if err != nil {
39 | fmt.Printf("start producer error: %s", err.Error())
40 | os.Exit(1)
41 | }
42 | var wg sync.WaitGroup
43 | for i := 0; i < 10; i++ {
44 | wg.Add(1)
45 | err := p.SendAsync(context.Background(),
46 | func(ctx context.Context, result *primitive.SendResult, e error) {
47 | if e != nil {
48 | fmt.Printf("receive message error: %s\n", err)
49 | } else {
50 | fmt.Printf("send message success: result=%s\n", result.String())
51 | }
52 | wg.Done()
53 | }, primitive.NewMessage("test", []byte("Hello RocketMQ Go Client!")))
54 |
55 | if err != nil {
56 | fmt.Printf("send message error: %s\n", err)
57 | }
58 | }
59 | wg.Wait()
60 | err = p.Shutdown()
61 | if err != nil {
62 | fmt.Printf("shutdown producer error: %s", err.Error())
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/examples/producer/batch/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "strconv"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/primitive"
28 | "github.com/apache/rocketmq-client-go/v2/producer"
29 | )
30 |
31 | func main() {
32 | p, _ := rocketmq.NewProducer(
33 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
34 | producer.WithRetry(2),
35 | )
36 | err := p.Start()
37 | if err != nil {
38 | fmt.Printf("start producer error: %s", err.Error())
39 | os.Exit(1)
40 | }
41 | var msgs []*primitive.Message
42 | for i := 0; i < 10; i++ {
43 | msgs = append(msgs, primitive.NewMessage("test",
44 | []byte("Hello RocketMQ Go Client! num: "+strconv.Itoa(i))))
45 | }
46 |
47 | res, err := p.SendSync(context.Background(), msgs...)
48 |
49 | if err != nil {
50 | fmt.Printf("send message error: %s\n", err)
51 | } else {
52 | fmt.Printf("send message success: result=%s\n", res.String())
53 | }
54 | err = p.Shutdown()
55 | if err != nil {
56 | fmt.Printf("shutdown producer error: %s", err.Error())
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/examples/producer/delay/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 |
25 | "github.com/apache/rocketmq-client-go/v2"
26 | "github.com/apache/rocketmq-client-go/v2/primitive"
27 | "github.com/apache/rocketmq-client-go/v2/producer"
28 | )
29 |
30 | func main() {
31 | p, _ := rocketmq.NewProducer(
32 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
33 | producer.WithRetry(2),
34 | )
35 | err := p.Start()
36 | if err != nil {
37 | fmt.Printf("start producer error: %s", err.Error())
38 | os.Exit(1)
39 | }
40 | for i := 0; i < 10; i++ {
41 | msg := primitive.NewMessage("test", []byte("Hello RocketMQ Go Client!"))
42 | msg.WithDelayTimeLevel(3)
43 | res, err := p.SendSync(context.Background(), msg)
44 |
45 | if err != nil {
46 | fmt.Printf("send message error: %s\n", err)
47 | } else {
48 | fmt.Printf("send message success: result=%s\n", res.String())
49 | }
50 | }
51 | err = p.Shutdown()
52 | if err != nil {
53 | fmt.Printf("shutdown producer error: %s", err.Error())
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/examples/producer/delayms/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "flag"
6 | "fmt"
7 | "os"
8 | "strconv"
9 | "time"
10 |
11 | "github.com/apache/rocketmq-client-go/v2"
12 | "github.com/apache/rocketmq-client-go/v2/primitive"
13 | "github.com/apache/rocketmq-client-go/v2/producer"
14 | )
15 |
16 | var (
17 | topic string
18 | endpoint string
19 | accessKey string
20 | secretKey string
21 | messageCount int
22 | delayInterval int
23 | )
24 |
25 | func init() {
26 | flag.StringVar(&topic, "topic", "benchmark-queue-1", "topic name")
27 | flag.StringVar(&endpoint, "endpoint", "127.0.0.1:9876", "endpoint")
28 | flag.StringVar(&accessKey, "access_key", "******", "access key")
29 | flag.StringVar(&secretKey, "secret_key", "******", "secret key")
30 | flag.IntVar(&messageCount, "count", 10, "message count")
31 | flag.IntVar(&delayInterval, "delay", 0, "delay interval unit: sec")
32 | }
33 |
34 | // Package main implements a simple producer to send message.
35 | func main() {
36 | flag.Parse()
37 |
38 | p, err := rocketmq.NewProducer(
39 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{endpoint})),
40 | producer.WithRetry(2),
41 | )
42 | if err != nil {
43 | fmt.Println(err)
44 | os.Exit(1)
45 | }
46 | err = p.Start()
47 | if err != nil {
48 | fmt.Printf("start producer error: %s", err.Error())
49 | os.Exit(1)
50 | }
51 |
52 | for i := 0; i < messageCount; i++ {
53 | msg := &primitive.Message{
54 | Topic: topic,
55 | Body: []byte("Hello RocketMQ Go Client! " + strconv.Itoa(i) +
56 | " timestamp:" + strconv.FormatInt(time.Now().Unix(), 10)),
57 | }
58 | if delayInterval > 0 {
59 | msg.WithDelayTimestamp(time.Now().Add(time.Duration(delayInterval) * time.Second))
60 | }
61 | res, err := p.SendSync(context.Background(), msg)
62 |
63 | if err != nil {
64 | fmt.Printf("index: %v, send message error: %s\n", i+1, err)
65 | break
66 | } else {
67 | fmt.Printf("index: %v, send message success: result=%s\n", i+1, res.String())
68 | }
69 | time.Sleep(100 * time.Millisecond)
70 | }
71 | err = p.Shutdown()
72 | if err != nil {
73 | fmt.Printf("shutdown producer error: %s", err.Error())
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/examples/producer/interceptor/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | // Package main implements a producer with user custom interceptor.
19 | package main
20 |
21 | import (
22 | "context"
23 | "fmt"
24 | "os"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/primitive"
28 | "github.com/apache/rocketmq-client-go/v2/producer"
29 | )
30 |
31 | func main() {
32 | p, _ := rocketmq.NewProducer(
33 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
34 | producer.WithRetry(2),
35 | producer.WithInterceptor(UserFirstInterceptor(), UserSecondInterceptor()),
36 | )
37 | err := p.Start()
38 | if err != nil {
39 | fmt.Printf("start producer error: %s", err.Error())
40 | os.Exit(1)
41 | }
42 | for i := 0; i < 10; i++ {
43 | res, err := p.SendSync(context.Background(), primitive.NewMessage("test",
44 | []byte("Hello RocketMQ Go Client!")))
45 |
46 | if err != nil {
47 | fmt.Printf("send message error: %s\n", err)
48 | } else {
49 | fmt.Printf("send message success: result=%s\n", res.String())
50 | }
51 | }
52 | err = p.Shutdown()
53 | if err != nil {
54 | fmt.Printf("shutdown producer error: %s", err.Error())
55 | }
56 | }
57 |
58 | func UserFirstInterceptor() primitive.Interceptor {
59 | return func(ctx context.Context, req, reply interface{}, next primitive.Invoker) error {
60 | fmt.Printf("user first interceptor before invoke: req:%v\n", req)
61 | err := next(ctx, req, reply)
62 | fmt.Printf("user first interceptor after invoke: req: %v, reply: %v \n", req, reply)
63 | return err
64 | }
65 | }
66 |
67 | func UserSecondInterceptor() primitive.Interceptor {
68 | return func(ctx context.Context, req, reply interface{}, next primitive.Invoker) error {
69 | fmt.Printf("user second interceptor before invoke: req: %v\n", req)
70 | err := next(ctx, req, reply)
71 | fmt.Printf("user second interceptor after invoke: req: %v, reply: %v \n", req, reply)
72 | return err
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/examples/producer/namespace/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | // Package main implements a producer with user custom interceptor.
19 | package main
20 |
21 | import (
22 | "context"
23 | "fmt"
24 | "os"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/primitive"
28 | "github.com/apache/rocketmq-client-go/v2/producer"
29 | )
30 |
31 | func main() {
32 | p, err := rocketmq.NewProducer(
33 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
34 | producer.WithRetry(2),
35 | producer.WithCredentials(primitive.Credentials{
36 | AccessKey: "RocketMQ",
37 | SecretKey: "12345678",
38 | }),
39 | producer.WithNamespace("namespace"),
40 | )
41 |
42 | if err != nil {
43 | fmt.Println("init producer error: " + err.Error())
44 | os.Exit(0)
45 | }
46 |
47 | err = p.Start()
48 | if err != nil {
49 | fmt.Printf("start producer error: %s", err.Error())
50 | os.Exit(1)
51 | }
52 | for i := 0; i < 100000; i++ {
53 | res, err := p.SendSync(context.Background(), primitive.NewMessage("test",
54 | []byte("Hello RocketMQ Go Client!")))
55 |
56 | if err != nil {
57 | fmt.Printf("send message error: %s\n", err)
58 | } else {
59 | fmt.Printf("send message success: result=%s\n", res.String())
60 | }
61 | }
62 | err = p.Shutdown()
63 | if err != nil {
64 | fmt.Printf("shutdown producer error: %s", err.Error())
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/examples/producer/rpc/async/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/primitive"
28 | "github.com/apache/rocketmq-client-go/v2/producer"
29 | )
30 |
31 | func main() {
32 | p, _ := rocketmq.NewProducer(
33 | producer.WithGroupName("please_rename_unique_group_name"),
34 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | producer.WithRetry(2),
36 | )
37 | err := p.Start()
38 | if err != nil {
39 | fmt.Printf("start producer error: %s", err.Error())
40 | os.Exit(1)
41 | }
42 |
43 | topic := "RequestTopic"
44 | ttl := 5 * time.Second
45 | msg := &primitive.Message{
46 | Topic: topic,
47 | Body: []byte("Hello RPC RocketMQ Go Client!"),
48 | }
49 |
50 | now := time.Now()
51 | f := func(ctx context.Context, responseMsg *primitive.Message, respErr error) {
52 | if respErr != nil {
53 | fmt.Printf("request to <%s> fail, err:%v \n", topic, respErr)
54 | return
55 | }
56 | fmt.Printf("Requst to %s cost:%d ms responseMsg:%s\n", topic, time.Since(now)/time.Millisecond, responseMsg.String())
57 | }
58 | err = p.RequestAsync(context.Background(), ttl, f, msg)
59 | if err != nil {
60 | fmt.Printf("Request Async message error: %s\n", err)
61 | return
62 | }
63 |
64 | time.Sleep(time.Minute)
65 | err = p.Shutdown()
66 | if err != nil {
67 | fmt.Printf("shutdown producer error: %s", err.Error())
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/examples/producer/rpc/sync/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/primitive"
28 | "github.com/apache/rocketmq-client-go/v2/producer"
29 | )
30 |
31 | func main() {
32 | p, _ := rocketmq.NewProducer(
33 | producer.WithGroupName("please_rename_unique_group_name"),
34 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | producer.WithRetry(2),
36 | )
37 | err := p.Start()
38 | if err != nil {
39 | fmt.Printf("start producer error: %s", err.Error())
40 | os.Exit(1)
41 | }
42 |
43 | topic := "RequestTopic"
44 | ttl := 5 * time.Second
45 | msg := &primitive.Message{
46 | Topic: topic,
47 | Body: []byte("Hello RPC RocketMQ Go Client!"),
48 | }
49 |
50 | now := time.Now()
51 | responseMsg, err := p.Request(context.Background(), ttl, msg)
52 | if err != nil {
53 | fmt.Printf("Request message error: %s\n", err)
54 | return
55 | }
56 | fmt.Printf("Requst to %s cost:%d ms responseMsg:%s\n", topic, time.Since(now)/time.Millisecond, responseMsg.String())
57 |
58 | err = p.Shutdown()
59 | if err != nil {
60 | fmt.Printf("shutdown producer error: %s", err.Error())
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/examples/producer/simple/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "strconv"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/primitive"
28 | "github.com/apache/rocketmq-client-go/v2/producer"
29 | )
30 |
31 | // Package main implements a simple producer to send message.
32 | func main() {
33 | p, _ := rocketmq.NewProducer(
34 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | producer.WithRetry(2),
36 | )
37 | err := p.Start()
38 | if err != nil {
39 | fmt.Printf("start producer error: %s", err.Error())
40 | os.Exit(1)
41 | }
42 | topic := "test"
43 |
44 | for i := 0; i < 10; i++ {
45 | msg := &primitive.Message{
46 | Topic: topic,
47 | Body: []byte("Hello RocketMQ Go Client! " + strconv.Itoa(i)),
48 | }
49 | res, err := p.SendSync(context.Background(), msg)
50 |
51 | if err != nil {
52 | fmt.Printf("send message error: %s\n", err)
53 | } else {
54 | fmt.Printf("send message success: result=%s\n", res.String())
55 | }
56 | }
57 | err = p.Shutdown()
58 | if err != nil {
59 | fmt.Printf("shutdown producer error: %s", err.Error())
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/examples/producer/tag/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 |
25 | "github.com/apache/rocketmq-client-go/v2"
26 | "github.com/apache/rocketmq-client-go/v2/primitive"
27 | "github.com/apache/rocketmq-client-go/v2/producer"
28 | )
29 |
30 | func main() {
31 | p, _ := rocketmq.NewProducer(
32 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
33 | producer.WithRetry(2),
34 | )
35 | err := p.Start()
36 | if err != nil {
37 | fmt.Printf("start producer error: %s", err.Error())
38 | os.Exit(1)
39 | }
40 | tags := []string{"TagA", "TagB", "TagC"}
41 | for i := 0; i < 3; i++ {
42 | tag := tags[i%3]
43 | msg := primitive.NewMessage("test",
44 | []byte("Hello RocketMQ Go Client!"))
45 | msg.WithTag(tag)
46 |
47 | res, err := p.SendSync(context.Background(), msg)
48 | if err != nil {
49 | fmt.Printf("send message error: %s\n", err)
50 | } else {
51 | fmt.Printf("send message success: result=%s\n", res.String())
52 | }
53 | }
54 | err = p.Shutdown()
55 | if err != nil {
56 | fmt.Printf("shutdown producer error: %s", err.Error())
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/examples/producer/tls/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "strconv"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/primitive"
28 | "github.com/apache/rocketmq-client-go/v2/producer"
29 | )
30 |
31 | // Package main implements a simple producer to send message.
32 | func main() {
33 | p, _ := rocketmq.NewProducer(
34 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
35 | producer.WithRetry(2),
36 | producer.WithTls(true),
37 | )
38 | err := p.Start()
39 | if err != nil {
40 | fmt.Printf("start producer error: %s", err.Error())
41 | os.Exit(1)
42 | }
43 | topic := "test"
44 |
45 | for i := 0; i < 10; i++ {
46 | msg := &primitive.Message{
47 | Topic: topic,
48 | Body: []byte("Hello RocketMQ Go Client! " + strconv.Itoa(i)),
49 | }
50 | res, err := p.SendSync(context.Background(), msg)
51 |
52 | if err != nil {
53 | fmt.Printf("send message error: %s\n", err)
54 | } else {
55 | fmt.Printf("send message success: result=%s\n", res.String())
56 | }
57 | }
58 | err = p.Shutdown()
59 | if err != nil {
60 | fmt.Printf("shutdown producer error: %s", err.Error())
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/examples/producer/trace/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2"
27 | "github.com/apache/rocketmq-client-go/v2/primitive"
28 | "github.com/apache/rocketmq-client-go/v2/producer"
29 | )
30 |
31 | func main() {
32 | namesrvs := []string{"127.0.0.1:9876"}
33 | traceCfg := &primitive.TraceConfig{
34 | Access: primitive.Local,
35 | Resolver: primitive.NewPassthroughResolver(namesrvs),
36 | }
37 |
38 | p, _ := rocketmq.NewProducer(
39 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
40 | producer.WithRetry(2),
41 | producer.WithTrace(traceCfg))
42 | err := p.Start()
43 | if err != nil {
44 | fmt.Printf("start producer error: %s", err.Error())
45 | os.Exit(1)
46 | }
47 | for i := 0; i < 1; i++ {
48 | res, err := p.SendSync(context.Background(), primitive.NewMessage("test",
49 | []byte("Hello RocketMQ Go Client!")))
50 |
51 | if err != nil {
52 | fmt.Printf("send message error: %s\n", err)
53 | } else {
54 | fmt.Printf("send message success: result=%s\n", res.String())
55 | }
56 | }
57 |
58 | time.Sleep(10 * time.Second)
59 |
60 | err = p.Shutdown()
61 | if err != nil {
62 | fmt.Printf("shutdown producer error: %s", err.Error())
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/examples/producer/transaction/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "strconv"
25 | "sync"
26 | "sync/atomic"
27 | "time"
28 |
29 | "github.com/apache/rocketmq-client-go/v2"
30 | "github.com/apache/rocketmq-client-go/v2/primitive"
31 | "github.com/apache/rocketmq-client-go/v2/producer"
32 | )
33 |
34 | type DemoListener struct {
35 | localTrans *sync.Map
36 | transactionIndex int32
37 | }
38 |
39 | func NewDemoListener() *DemoListener {
40 | return &DemoListener{
41 | localTrans: new(sync.Map),
42 | }
43 | }
44 |
45 | func (dl *DemoListener) ExecuteLocalTransaction(msg *primitive.Message) primitive.LocalTransactionState {
46 | nextIndex := atomic.AddInt32(&dl.transactionIndex, 1)
47 | fmt.Printf("nextIndex: %v for transactionID: %v\n", nextIndex, msg.TransactionId)
48 | status := nextIndex % 3
49 | dl.localTrans.Store(msg.TransactionId, primitive.LocalTransactionState(status+1))
50 |
51 | fmt.Printf("dl")
52 | return primitive.UnkonwnState
53 | }
54 |
55 | func (dl *DemoListener) CheckLocalTransaction(msg *primitive.MessageExt) primitive.LocalTransactionState {
56 | fmt.Printf("%v msg transactionID : %v\n", time.Now(), msg.TransactionId)
57 | v, existed := dl.localTrans.Load(msg.TransactionId)
58 | if !existed {
59 | fmt.Printf("unknown msg: %v, return Commit", msg)
60 | return primitive.CommitMessageState
61 | }
62 | state := v.(primitive.LocalTransactionState)
63 | switch state {
64 | case 1:
65 | fmt.Printf("checkLocalTransaction COMMIT_MESSAGE: %v\n", msg)
66 | return primitive.CommitMessageState
67 | case 2:
68 | fmt.Printf("checkLocalTransaction ROLLBACK_MESSAGE: %v\n", msg)
69 | return primitive.RollbackMessageState
70 | case 3:
71 | fmt.Printf("checkLocalTransaction unknown: %v\n", msg)
72 | return primitive.UnkonwnState
73 | default:
74 | fmt.Printf("checkLocalTransaction default COMMIT_MESSAGE: %v\n", msg)
75 | return primitive.CommitMessageState
76 | }
77 | }
78 |
79 | func main() {
80 | p, _ := rocketmq.NewTransactionProducer(
81 | NewDemoListener(),
82 | producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
83 | producer.WithRetry(1),
84 | )
85 | err := p.Start()
86 | if err != nil {
87 | fmt.Printf("start producer error: %s\n", err.Error())
88 | os.Exit(1)
89 | }
90 |
91 | for i := 0; i < 10; i++ {
92 | res, err := p.SendMessageInTransaction(context.Background(),
93 | primitive.NewMessage("TopicTest5", []byte("Hello RocketMQ again "+strconv.Itoa(i))))
94 |
95 | if err != nil {
96 | fmt.Printf("send message error: %s\n", err)
97 | } else {
98 | fmt.Printf("send message success: result=%s\n", res.String())
99 | }
100 | }
101 | time.Sleep(5 * time.Minute)
102 | err = p.Shutdown()
103 | if err != nil {
104 | fmt.Printf("shutdown producer error: %s", err.Error())
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/apache/rocketmq-client-go/v2
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/BurntSushi/toml v1.1.0 // indirect
7 | github.com/emirpasic/gods v1.12.0
8 | github.com/go-redis/redis/v8 v8.11.5
9 | github.com/golang/mock v1.3.1
10 | github.com/google/uuid v1.3.0
11 | github.com/json-iterator/go v1.1.12
12 | github.com/patrickmn/go-cache v2.1.0+incompatible
13 | github.com/pkg/errors v0.8.1
14 | github.com/sirupsen/logrus v1.4.0
15 | github.com/smartystreets/goconvey v1.6.4
16 | github.com/stretchr/testify v1.5.1
17 | github.com/tidwall/gjson v1.13.0
18 | go.uber.org/atomic v1.5.1
19 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
20 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
21 | gopkg.in/natefinch/lumberjack.v2 v2.0.0
22 | stathat.com/c/consistent v1.0.0
23 | )
24 |
--------------------------------------------------------------------------------
/hooks/filter_message_hook.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package hooks
19 |
20 | import "github.com/apache/rocketmq-client-go/v2/primitive"
21 |
22 | type FilterMessageContext struct {
23 | ConsumerGroup string
24 | Msg []*primitive.MessageExt
25 | MQ *primitive.MessageQueue
26 | Arg interface{}
27 | UnitMode bool
28 | }
29 |
30 | type FilterMessageHook func(ctx *FilterMessageContext) ([]*primitive.MessageExt, error)
31 |
--------------------------------------------------------------------------------
/internal/callback.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package internal
19 |
20 | import (
21 | "net"
22 |
23 | "github.com/apache/rocketmq-client-go/v2/primitive"
24 | )
25 |
26 | // remotingClient callback TransactionProducer
27 | type CheckTransactionStateCallback struct {
28 | Addr net.Addr
29 | Msg *primitive.MessageExt
30 | Header CheckTransactionStateRequestHeader
31 | }
32 |
--------------------------------------------------------------------------------
/internal/constants.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package internal
19 |
20 | import "strings"
21 |
22 | const (
23 | RetryGroupTopicPrefix = "%RETRY%"
24 | DefaultConsumerGroup = "DEFAULT_CONSUMER"
25 | ClientInnerProducerGroup = "CLIENT_INNER_PRODUCER"
26 | SystemTopicPrefix = "rmq_sys_"
27 | ReplyMessageFlag = "reply"
28 | ReplyTopicPostfix = "REPLY_TOPIC"
29 | )
30 |
31 | func GetReplyTopic(clusterName string) string {
32 | return clusterName + "_" + ReplyTopicPostfix
33 | }
34 |
35 | func GetRetryTopic(group string) string {
36 | if strings.HasPrefix(group, RetryGroupTopicPrefix) {
37 | return group
38 | }
39 | return RetryGroupTopicPrefix + group
40 | }
41 |
--------------------------------------------------------------------------------
/internal/mq_version.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing software
12 | distributed under the License is distributed on an "AS IS" BASIS
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package internal
19 |
20 | const (
21 | V4_1_0 = 0
22 | )
23 |
--------------------------------------------------------------------------------
/internal/perm.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package internal
19 |
20 | const (
21 | permPriority = 0x1 << 3
22 | permRead = 0x1 << 2
23 | permWrite = 0x1 << 1
24 | permInherit = 0x1 << 0
25 | )
26 |
27 | func queueIsReadable(perm int) bool {
28 | return (perm & permRead) == permRead
29 | }
30 |
31 | func queueIsWriteable(perm int) bool {
32 | return (perm & permWrite) == permWrite
33 | }
34 |
35 | func queueIsInherited(perm int) bool {
36 | return (perm & permInherit) == permInherit
37 | }
38 |
39 | func perm2string(perm int) string {
40 | bytes := make([]byte, 3)
41 | for i := 0; i < 3; i++ {
42 | bytes[i] = '-'
43 | }
44 |
45 | if queueIsReadable(perm) {
46 | bytes[0] = 'R'
47 | }
48 |
49 | if queueIsWriteable(perm) {
50 | bytes[1] = 'W'
51 | }
52 |
53 | if queueIsInherited(perm) {
54 | bytes[2] = 'X'
55 | }
56 |
57 | return string(bytes)
58 | }
59 |
--------------------------------------------------------------------------------
/internal/remote/future.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package remote
19 |
20 | import (
21 | "context"
22 | "github.com/apache/rocketmq-client-go/v2/errors"
23 | "sync"
24 | )
25 |
26 | // ResponseFuture
27 | type ResponseFuture struct {
28 | ResponseCommand *RemotingCommand
29 | Err error
30 | Opaque int32
31 | callback func(*ResponseFuture)
32 | Done chan bool
33 | callbackOnce sync.Once
34 | ctx context.Context
35 | }
36 |
37 | // NewResponseFuture create ResponseFuture with opaque, timeout and callback
38 | func NewResponseFuture(ctx context.Context, opaque int32, callback func(*ResponseFuture)) *ResponseFuture {
39 | return &ResponseFuture{
40 | Opaque: opaque,
41 | Done: make(chan bool),
42 | callback: callback,
43 | ctx: ctx,
44 | }
45 | }
46 |
47 | func (r *ResponseFuture) executeInvokeCallback() {
48 | r.callbackOnce.Do(func() {
49 | if r.callback != nil {
50 | r.callback(r)
51 | }
52 | })
53 | }
54 |
55 | func (r *ResponseFuture) waitResponse() (*RemotingCommand, error) {
56 | var (
57 | cmd *RemotingCommand
58 | err error
59 | )
60 | select {
61 | case <-r.Done:
62 | cmd, err = r.ResponseCommand, r.Err
63 | case <-r.ctx.Done():
64 | err = errors.ErrRequestTimeout
65 | r.Err = err
66 | }
67 | return cmd, err
68 | }
69 |
--------------------------------------------------------------------------------
/internal/remote/interceptor.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package remote
19 |
20 | import (
21 | "context"
22 | "crypto/hmac"
23 | "crypto/sha1"
24 | "encoding/base64"
25 | "hash"
26 | "sort"
27 | "strings"
28 |
29 | "github.com/apache/rocketmq-client-go/v2/primitive"
30 | )
31 |
32 | const (
33 | signature = "Signature"
34 | accessKey = "AccessKey"
35 | securityToken = "SecurityToken"
36 | keyFile = "KEY_FILE"
37 | // System.getProperty("rocketmq.client.keyFile", System.getProperty("user.home") + File.separator + "key");
38 | )
39 |
40 | func ACLInterceptor(credentials primitive.Credentials) primitive.Interceptor {
41 | return func(ctx context.Context, req, reply interface{}, next primitive.Invoker) error {
42 | cmd := req.(*RemotingCommand)
43 | m := make(map[string]string)
44 | order := make([]string, 1)
45 | m[accessKey] = credentials.AccessKey
46 | order[0] = accessKey
47 | if credentials.SecurityToken != "" {
48 | m[securityToken] = credentials.SecurityToken
49 | }
50 | for k, v := range cmd.ExtFields {
51 | m[k] = v
52 | order = append(order, k)
53 | }
54 | sort.Slice(order, func(i, j int) bool {
55 | return strings.Compare(order[i], order[j]) < 0
56 | })
57 | content := ""
58 | for idx := range order {
59 | content += m[order[idx]]
60 | }
61 | buf := make([]byte, len(content)+len(cmd.Body))
62 | copy(buf, []byte(content))
63 | copy(buf[len(content):], cmd.Body)
64 |
65 | cmd.ExtFields[signature] = calculateSignature(buf, []byte(credentials.SecretKey))
66 | cmd.ExtFields[accessKey] = credentials.AccessKey
67 |
68 | // The SecurityToken value is unnecessary, user can choose this one.
69 | if credentials.SecurityToken != "" {
70 | cmd.ExtFields[securityToken] = credentials.SecurityToken
71 | }
72 | err := next(ctx, req, reply)
73 | return err
74 | }
75 | }
76 |
77 | func calculateSignature(data, sk []byte) string {
78 | mac := hmac.New(func() hash.Hash {
79 | return sha1.New()
80 | }, sk)
81 | mac.Write(data)
82 | return base64.StdEncoding.EncodeToString(mac.Sum(nil))
83 | }
84 |
--------------------------------------------------------------------------------
/internal/remote/interceptor_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package remote
19 |
20 | import (
21 | "testing"
22 |
23 | "github.com/stretchr/testify/assert"
24 | )
25 |
26 | func Test_CalculateSignature(t *testing.T) {
27 | assert.Equal(t, "tAb/54Rwwcq+pbH8Loi7FWX4QSQ=",
28 | calculateSignature([]byte("Hello RocketMQ Client ACL Feature"), []byte("adiaushdiaushd")))
29 | }
30 |
--------------------------------------------------------------------------------
/internal/remote/rpchook.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package remote
19 |
20 | type RPCHook interface {
21 | DoBeforeRequest(string, *RemotingCommand)
22 | DoAfterResponse(string, *RemotingCommand)
23 | }
24 |
--------------------------------------------------------------------------------
/internal/remote/tcp_conn.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package remote
18 |
19 | import (
20 | "context"
21 | "crypto/tls"
22 | "net"
23 | "sync"
24 | "time"
25 |
26 | "go.uber.org/atomic"
27 | )
28 |
29 | // TODO: Adding TCP Connections Pool, https://github.com/apache/rocketmq-client-go/v2/issues/298
30 | type tcpConnWrapper struct {
31 | net.Conn
32 | sync.Mutex
33 | closed atomic.Bool
34 | }
35 |
36 | func initConn(ctx context.Context, addr string, config *RemotingClientConfig) (*tcpConnWrapper, error) {
37 | var d net.Dialer
38 | d.KeepAlive = config.KeepAliveDuration
39 | d.Deadline = time.Now().Add(config.ConnectionTimeout)
40 |
41 | var conn net.Conn
42 | var err error
43 | if config.UseTls {
44 | conn, err = tls.DialWithDialer(&d, "tcp", addr, &tls.Config{
45 | InsecureSkipVerify: true,
46 | })
47 | } else {
48 | conn, err = d.DialContext(ctx, "tcp", addr)
49 | }
50 |
51 | if err != nil {
52 | return nil, err
53 | }
54 | return &tcpConnWrapper{
55 | Conn: conn,
56 | }, nil
57 | }
58 |
59 | func (wrapper *tcpConnWrapper) destroy() error {
60 | wrapper.closed.Swap(true)
61 | return wrapper.Conn.Close()
62 | }
63 |
64 | func (wrapper *tcpConnWrapper) isClosed(err error) bool {
65 | if !wrapper.closed.Load() {
66 | return false
67 | }
68 |
69 | opErr, ok := err.(*net.OpError)
70 | if !ok {
71 | return false
72 | }
73 |
74 | return opErr.Err.Error() == "use of closed network connection"
75 | }
76 |
--------------------------------------------------------------------------------
/internal/response.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package internal
19 |
20 | const (
21 | ResSuccess = int16(0)
22 | ResError = int16(1)
23 | ResFlushDiskTimeout = int16(10)
24 | ResSlaveNotAvailable = int16(11)
25 | ResFlushSlaveTimeout = int16(12)
26 | ResServiceNotAvailable = int16(14)
27 | ResNoPermission = int16(16)
28 | ResTopicNotExist = int16(17)
29 | ResPullNotFound = int16(19)
30 | ResPullRetryImmediately = int16(20)
31 | ResPullOffsetMoved = int16(21)
32 | ResQueryNotFound = int16(22)
33 | )
34 |
35 | type SendMessageResponse struct {
36 | MsgId string
37 | QueueId int32
38 | QueueOffset int64
39 | TransactionId string
40 | MsgRegion string
41 | }
42 |
43 | func (response *SendMessageResponse) Decode(properties map[string]string) {
44 |
45 | }
46 |
47 | type PullMessageResponse struct {
48 | SuggestWhichBrokerId int64
49 | NextBeginOffset int64
50 | MinOffset int64
51 | MaxOffset int64
52 | }
53 |
--------------------------------------------------------------------------------
/internal/trace_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package internal
19 |
20 | import (
21 | "testing"
22 |
23 | "github.com/apache/rocketmq-client-go/v2/primitive"
24 | . "github.com/smartystreets/goconvey/convey"
25 | "github.com/stretchr/testify/assert"
26 | )
27 |
28 | func TestMarshal2Bean(t *testing.T) {
29 |
30 | Convey("marshal of TraceContext", t, func() {
31 |
32 | Convey("When marshal producer trace data", func() {
33 | traceCtx := TraceContext{
34 | TraceType: Pub,
35 | TimeStamp: 1563780533299,
36 | RegionId: "DefaultRegion",
37 | GroupName: "ProducerGroupName",
38 | CostTime: 3572,
39 | IsSuccess: true,
40 | RequestId: "0A5DE93A815518B4AAC26F77F8330001",
41 | TraceBeans: []TraceBean{
42 | {
43 | Topic: "TopicTest",
44 | MsgId: "0A5DE93A833B18B4AAC26F842A2F0000",
45 | OffsetMsgId: "0A5DE93A00002A9F000000000042E322",
46 | Tags: "TagA",
47 | Keys: "OrderID1882",
48 | StoreHost: "10.93.233.58:10911",
49 | ClientHost: "10.93.233.58",
50 | StoreTime: 1563780535085,
51 | BodyLength: 11,
52 | MsgType: primitive.NormalMsg,
53 | },
54 | },
55 | }
56 | bean := traceCtx.marshal2Bean()
57 | assert.Equal(t, "Pub1563780533299DefaultRegionProducerGroupNameTopicTest0A5DE93A833B18B4AAC26F842A2F0000TagAOrderID188210.93.233.58:1091111357200A5DE93A00002A9F000000000042E322true10.93.233.58\x02",
58 | bean.transData)
59 | assert.Equal(t, []string{"0A5DE93A833B18B4AAC26F842A2F0000", "OrderID1882"}, bean.transKey)
60 |
61 | // consumer before test
62 | traceCtx = TraceContext{
63 | TraceType: SubBefore,
64 | TimeStamp: 1563789119096,
65 | GroupName: "CID_JODIE_1",
66 | IsSuccess: true,
67 | RequestId: "0A5DE93A96A818B4AAC26FFAFA780007",
68 | TraceBeans: []TraceBean{
69 | {
70 | Topic: "TopicTest",
71 | MsgId: "0A5DE93A973418B4AAC26FFAFA5A0000",
72 | Tags: "TagA",
73 | Keys: "OrderID1882",
74 | StoreHost: "10.93.233.58",
75 | ClientHost: "10.93.233.58",
76 | StoreTime: 1563789119092,
77 | BodyLength: 190,
78 | },
79 | },
80 | }
81 | bean = traceCtx.marshal2Bean()
82 |
83 | Convey("transData should equal to expected", func() {
84 | So(bean.transData, ShouldEqual, "SubBefore1563789119096CID_JODIE_10A5DE93A96A818B4AAC26FFAFA7800070A5DE93A973418B4AAC26FFAFA5A00000OrderID188210.93.233.58")
85 | })
86 |
87 | Convey("transkey should equal to expected", func() {
88 | expectedKey := []string{"0A5DE93A973418B4AAC26FFAFA5A0000", "OrderID1882"}
89 | So(bean.transKey[0], ShouldEqual, expectedKey[0])
90 | So(bean.transKey[1], ShouldEqual, expectedKey[1])
91 | })
92 | })
93 |
94 | Convey("When marshal consumer trace data", func() {
95 | traceCtx := TraceContext{
96 | TraceType: SubAfter,
97 | TimeStamp: 1563789119096,
98 | GroupName: "CID_JODIE_1",
99 | IsSuccess: true,
100 | RequestId: "0A5DE93A96A818B4AAC26FFAFA780007",
101 | TraceBeans: []TraceBean{
102 | {
103 | Topic: "TopicTest",
104 | MsgId: "0A5DE93A973418B4AAC26FFAFA5A0000",
105 | Tags: "TagA",
106 | Keys: "OrderID1882",
107 | StoreHost: "10.93.233.58",
108 | ClientHost: "10.93.233.58",
109 | StoreTime: 1563789119092,
110 | BodyLength: 190,
111 | },
112 | },
113 | }
114 | bean := traceCtx.marshal2Bean()
115 | Convey("transData should equal to expected", func() {
116 | So(bean.transData, ShouldEqual, "SubAfter0A5DE93A96A818B4AAC26FFAFA7800070A5DE93A973418B4AAC26FFAFA5A00000trueOrderID188201563789119096CID_JODIE_1")
117 | })
118 | Convey("transkey should equal to expected", func() {
119 | expectedKey := []string{"0A5DE93A973418B4AAC26FFAFA5A0000", "OrderID1882"}
120 | So(bean.transKey[0], ShouldEqual, expectedKey[0])
121 | So(bean.transKey[1], ShouldEqual, expectedKey[1])
122 | })
123 | })
124 | })
125 | }
126 |
--------------------------------------------------------------------------------
/internal/transaction.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package internal
19 |
20 | type TransactionListener interface {
21 | }
22 |
--------------------------------------------------------------------------------
/internal/utils/compression.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "bytes"
22 | "compress/zlib"
23 | "github.com/apache/rocketmq-client-go/v2/errors"
24 | "io/ioutil"
25 | "sync"
26 | )
27 |
28 | var zlibWriterPools []sync.Pool
29 |
30 | var bufPool = sync.Pool{
31 | New: func() interface{} {
32 | return &bytes.Buffer{}
33 | },
34 | }
35 |
36 | func init() {
37 | zlibWriterPools = make([]sync.Pool, zlib.BestCompression)
38 | for i := 0; i < zlib.BestCompression; i++ {
39 | compressLevel := i
40 | zlibWriterPools[i] = sync.Pool{
41 | New: func() interface{} {
42 | z, _ := zlib.NewWriterLevel(nil, compressLevel+1)
43 | return z
44 | },
45 | }
46 | }
47 | }
48 |
49 | func Compress(raw []byte, compressLevel int) ([]byte, error) {
50 | if compressLevel < zlib.BestSpeed || compressLevel > zlib.BestCompression {
51 | return nil, errors.ErrCompressLevel
52 | }
53 |
54 | buf := bufPool.Get().(*bytes.Buffer)
55 | defer bufPool.Put(buf)
56 | writerPool := &zlibWriterPools[compressLevel-1]
57 | writer := writerPool.Get().(*zlib.Writer)
58 | defer writerPool.Put(writer)
59 | buf.Reset()
60 | writer.Reset(buf)
61 | _, e := writer.Write(raw)
62 | if e != nil {
63 | return nil, e
64 | }
65 |
66 | e = writer.Close()
67 | if e != nil {
68 | return nil, e
69 | }
70 | result := make([]byte, buf.Len())
71 | buf.Read(result)
72 | return result, nil
73 | }
74 |
75 | func UnCompress(data []byte) []byte {
76 | rdata := bytes.NewReader(data)
77 | r, err := zlib.NewReader(rdata)
78 | if err != nil {
79 | return data
80 | }
81 | defer r.Close()
82 | retData, err := ioutil.ReadAll(r)
83 | if err != nil {
84 | return data
85 | }
86 | return retData
87 | }
88 |
--------------------------------------------------------------------------------
/internal/utils/compression_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "bytes"
22 | "compress/zlib"
23 | "encoding/json"
24 | "fmt"
25 | "math/rand"
26 | "testing"
27 | )
28 |
29 | func TestUnCompress(t *testing.T) {
30 | var b bytes.Buffer
31 | var oriStr string = "hello, go"
32 | zr := zlib.NewWriter(&b)
33 | zr.Write([]byte(oriStr))
34 | zr.Close()
35 |
36 | retBytes := UnCompress(b.Bytes())
37 | if string(retBytes) != oriStr {
38 | t.Errorf("UnCompress was incorrect, got %s, want: %s .", retBytes, []byte(oriStr))
39 | }
40 | }
41 |
42 | func TestCompress(t *testing.T) {
43 | raw := []byte("The quick brown fox jumps over the lazy dog")
44 | for i := zlib.BestSpeed; i <= zlib.BestCompression; i++ {
45 | compressed, e := Compress(raw, i)
46 | if e != nil {
47 | t.Errorf("Compress data:%s returns error: %v", string(raw), e)
48 | return
49 | }
50 | decompressed := UnCompress(compressed)
51 | if string(decompressed) != string(raw) {
52 | t.Errorf("data is corrupt, got: %s, want: %s", string(decompressed), string(raw))
53 | }
54 | }
55 | }
56 |
57 | func testCase(data []byte, level int, t *testing.T) {
58 | compressed, e := Compress(data, level)
59 | if e != nil {
60 | t.Errorf("Compress data:%v returns error: %v", data, e)
61 | }
62 | decompressed := UnCompress(compressed)
63 | if string(data) != string(decompressed) {
64 | t.Errorf("data is corrupt, got: %s, want: %s", string(decompressed), string(data))
65 | }
66 | }
67 |
68 | func generateRandTestData(n int) []byte {
69 | data := make([]byte, n)
70 | rand.Read(data)
71 | return data
72 | }
73 |
74 | func generateJsonString(n int) []byte {
75 | x := make(map[string]string)
76 | for i := 0; i < n; i++ {
77 | k := fmt.Sprintf("compression_key_%d", i)
78 | v := fmt.Sprintf("compression_value_%d", i)
79 | x[k] = v
80 | }
81 | data, _ := json.Marshal(x)
82 | return data
83 | }
84 |
85 | func TestCompressThreadSafe(t *testing.T) {
86 | for i := 0; i < 100; i++ {
87 | data := generateRandTestData(i * 100)
88 | level := i%zlib.BestCompression + 1
89 | go testCase(data, level, t)
90 | }
91 |
92 | for i := 0; i < 100; i++ {
93 | data := generateJsonString(i * 100)
94 | level := i%zlib.BestCompression + 1
95 | go testCase(data, level, t)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/internal/utils/errors.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "github.com/apache/rocketmq-client-go/v2/rlog"
22 | "runtime"
23 | )
24 |
25 | func CheckError(action string, err error) {
26 | if err != nil {
27 | rlog.Error(action, map[string]interface{}{
28 | rlog.LogKeyUnderlayError: err.Error(),
29 | })
30 | }
31 | }
32 | func GetStackAsString(all bool) string {
33 | buf := make([]byte, 1<<10)
34 | stackSize := runtime.Stack(buf, all)
35 | return string(buf[:stackSize])
36 | }
37 |
--------------------------------------------------------------------------------
/internal/utils/files.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "errors"
22 | "fmt"
23 | "os"
24 | "path/filepath"
25 | )
26 |
27 | func FileReadAll(path string) ([]byte, error) {
28 | stat, err := os.Stat(path)
29 | if err != nil {
30 | return nil, err
31 | }
32 | file, err := os.Open(path)
33 | if err != nil {
34 | return nil, err
35 | }
36 | data := make([]byte, stat.Size())
37 | _, err = file.Read(data)
38 | if err != nil {
39 | return nil, err
40 | }
41 | CheckError(fmt.Sprintf("close %s", file.Name()), file.Close())
42 | return data, nil
43 | }
44 |
45 | func ensureDir(path string) error {
46 | info, err := os.Stat(path)
47 | if err != nil {
48 | if os.IsNotExist(err) {
49 | err = os.MkdirAll(path, 0755)
50 | }
51 | return err
52 | }
53 | if !info.IsDir() {
54 | return errors.New(path + " is a file")
55 | }
56 | return nil
57 | }
58 |
59 | func WriteToFile(path string, data []byte) error {
60 | if err := ensureDir(filepath.Dir(path)); err != nil {
61 | return err
62 | }
63 | tmpFile, err := os.Create(path + ".tmp")
64 | if err != nil {
65 | return err
66 | }
67 | _, err = tmpFile.Write(data)
68 | if err != nil {
69 | return err
70 | }
71 | CheckError(fmt.Sprintf("close %s", tmpFile.Name()), tmpFile.Close())
72 |
73 | prevContent, err := FileReadAll(path)
74 | if err == nil {
75 | bakFile, err := os.Create(path + ".bak")
76 | if err != nil {
77 | _, err = bakFile.Write(prevContent)
78 | }
79 | if err != nil {
80 | return err
81 | }
82 | CheckError(fmt.Sprintf("close %s", bakFile.Name()), bakFile.Close())
83 | }
84 |
85 | _, err = os.Stat(path)
86 | if err == nil {
87 | CheckError(fmt.Sprintf("remove %s", path), os.Remove(path))
88 | }
89 | return os.Rename(path+".tmp", path)
90 | }
91 |
--------------------------------------------------------------------------------
/internal/utils/math.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package utils
19 |
20 | func AbsInt(i int) int {
21 | if i >= 0 {
22 | return i
23 | }
24 | return -i
25 | }
26 |
27 | func MinInt(a, b int) int {
28 | if a < b {
29 | return a
30 | }
31 | return b
32 | }
33 |
--------------------------------------------------------------------------------
/internal/utils/namespace.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "strings"
4 |
5 | const namespaceSeparator = "%"
6 | const retryPrefix = "%RETRY%"
7 | const dlqPrefix = "%DLQ%"
8 |
9 | func WrapNamespace(namespace, resourceWithOutNamespace string) string {
10 | if IsEmpty(namespace) || IsEmpty(resourceWithOutNamespace) {
11 | return resourceWithOutNamespace
12 | }
13 |
14 | if isAlreadyWithNamespace(resourceWithOutNamespace, namespace) {
15 | return resourceWithOutNamespace
16 | }
17 |
18 | return namespace + namespaceSeparator + resourceWithOutNamespace
19 | }
20 |
21 | func isAlreadyWithNamespace(resource, namespace string) bool {
22 | if IsEmpty(namespace) || IsEmpty(resource) {
23 | return false
24 | }
25 | return strings.Contains(resource, namespace+namespaceSeparator)
26 | }
27 |
28 | func WithoutNamespace(resource string) string {
29 | if len(resource) == 0 {
30 | return resource
31 | }
32 | resourceWithoutNamespace := ""
33 | if strings.HasPrefix(resource, retryPrefix) {
34 | resourceWithoutNamespace += retryPrefix
35 | } else if strings.HasPrefix(resource, dlqPrefix) {
36 | resourceWithoutNamespace += dlqPrefix
37 | }
38 | index := strings.LastIndex(resource, namespaceSeparator)
39 | if index > 0 {
40 | resourceWithoutNamespace += resource[index+1:]
41 | } else {
42 | resourceWithoutNamespace = resource
43 | }
44 | return resourceWithoutNamespace
45 | }
46 |
--------------------------------------------------------------------------------
/internal/utils/net.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "bytes"
22 | "fmt"
23 | "github.com/apache/rocketmq-client-go/v2/errors"
24 | "net"
25 | "strconv"
26 | "time"
27 | )
28 |
29 | var (
30 | LocalIP string
31 | )
32 |
33 | func init() {
34 | ip, err := ClientIP4()
35 | if err != nil {
36 | LocalIP = ""
37 | } else {
38 | LocalIP = fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
39 | }
40 | }
41 |
42 | func ClientIP4() ([]byte, error) {
43 | if ifaces, err := net.Interfaces(); err == nil && ifaces != nil {
44 | for _, iface := range ifaces {
45 | if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 {
46 | continue
47 | }
48 | if addrs, err := iface.Addrs(); err == nil && addrs != nil {
49 | for _, addr := range addrs {
50 | if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
51 | if ip4 := ipnet.IP.To4(); ip4 != nil {
52 | return ip4, nil
53 | }
54 | }
55 | }
56 | }
57 | }
58 | }
59 | return nil, errors.ErrUnknownIP
60 | }
61 |
62 | func FakeIP() []byte {
63 | buf := bytes.NewBufferString("")
64 | buf.WriteString(strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
65 | return buf.Bytes()[4:8]
66 | }
67 |
68 | func GetAddressByBytes(data []byte) string {
69 | return net.IPv4(data[0], data[1], data[2], data[3]).String()
70 | }
71 |
--------------------------------------------------------------------------------
/internal/utils/net_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package utils
19 |
20 | import "testing"
21 |
22 | func TestLocalIP2(t *testing.T) {
23 | t.Log(LocalIP)
24 | }
25 |
--------------------------------------------------------------------------------
/internal/utils/set.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "bytes"
22 | "encoding/json"
23 | "sort"
24 | )
25 |
26 | type UniqueItem interface {
27 | UniqueID() string
28 | }
29 |
30 | type StringUnique string
31 |
32 | func (str StringUnique) UniqueID() string {
33 | return string(str)
34 | }
35 |
36 | type Set struct {
37 | items map[string]UniqueItem
38 | }
39 |
40 | func NewSet() Set {
41 | return Set{
42 | items: make(map[string]UniqueItem, 0),
43 | }
44 | }
45 |
46 | func (s *Set) Items() map[string]UniqueItem {
47 | return s.items
48 | }
49 |
50 | func (s *Set) Add(v UniqueItem) {
51 | s.items[v.UniqueID()] = v
52 | }
53 |
54 | func (s *Set) AddKV(k, v string) {
55 | s.items[k] = StringUnique(v)
56 | }
57 |
58 | func (s *Set) Contains(k string) (UniqueItem, bool) {
59 | v, ok := s.items[k]
60 | return v, ok
61 | }
62 |
63 | func (s *Set) Len() int {
64 | return len(s.items)
65 | }
66 |
67 | var _ json.Marshaler = &Set{}
68 |
69 | func (s *Set) MarshalJSON() ([]byte, error) {
70 | if len(s.items) == 0 {
71 | return []byte("[]"), nil
72 | }
73 |
74 | buffer := new(bytes.Buffer)
75 | buffer.WriteByte('[')
76 | keys := make([]string, 0)
77 | for _, k := range s.items {
78 | var key string
79 | switch kval := k.(type) {
80 | case StringUnique:
81 | key = "\"" + string(kval) + "\""
82 | default:
83 | v, err := json.Marshal(k)
84 | if err != nil {
85 | return nil, err
86 | }
87 | key = string(v)
88 | }
89 | keys = append(keys, key)
90 | }
91 | sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
92 |
93 | for i, key := range keys {
94 | if i > 0 {
95 | buffer.WriteByte(',')
96 | }
97 | buffer.WriteString(key)
98 | }
99 |
100 | buffer.WriteByte(']')
101 |
102 | return buffer.Bytes(), nil
103 | }
104 |
105 | func (s *Set) UnmarshalJSON(data []byte) (err error) {
106 | return nil
107 | }
108 |
--------------------------------------------------------------------------------
/internal/utils/string.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package utils
19 |
20 | import "strings"
21 |
22 | // HashString hashes a string to a unique hashcode.
23 | func HashString(s string) int {
24 | val := []byte(s)
25 | var h int32
26 |
27 | for idx := range val {
28 | h = 31*h + int32(val[idx])
29 | }
30 |
31 | return int(h)
32 | }
33 |
34 | func IsEmpty(s string) bool {
35 | return strings.TrimSpace(s) == ""
36 | }
37 |
--------------------------------------------------------------------------------
/internal/validators.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package internal
19 |
20 | import (
21 | "errors"
22 | "fmt"
23 | "regexp"
24 | )
25 |
26 | const (
27 | _ValidPattern = "^[%|a-zA-Z0-9_-]+$"
28 | _CharacterMaxLength = 255
29 | )
30 |
31 | var (
32 | _Pattern = regexp.MustCompile(_ValidPattern)
33 | )
34 |
35 | func ValidateGroup(group string) error {
36 | if group == "" {
37 | return errors.New("consumerGroup is empty")
38 | }
39 | if len(group) > _CharacterMaxLength {
40 | return errors.New("the specified group is longer than group max length 255")
41 | }
42 | if !_Pattern.MatchString(group) {
43 | return fmt.Errorf("the specified group[%s] contains illegal characters, allowing only %s", group, _ValidPattern)
44 | }
45 | return nil
46 | }
47 |
--------------------------------------------------------------------------------
/primitive/auth.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package primitive
19 |
20 | type Credentials struct {
21 | AccessKey string
22 | SecretKey string
23 | SecurityToken string
24 | }
25 |
26 | func (c Credentials) IsEmpty() bool {
27 | return c.AccessKey == "" || c.SecretKey == ""
28 | }
29 |
--------------------------------------------------------------------------------
/primitive/base.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package primitive
19 |
20 | import (
21 | "github.com/apache/rocketmq-client-go/v2/errors"
22 | "regexp"
23 | "strings"
24 | )
25 |
26 | var (
27 | ipv4Regex, _ = regexp.Compile(`^((25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))`)
28 | ipv6Regex, _ = regexp.Compile(`(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`)
29 | )
30 |
31 | type NamesrvAddr []string
32 |
33 | func NewNamesrvAddr(s ...string) (NamesrvAddr, error) {
34 | if len(s) == 0 {
35 | return nil, errors.ErrNoNameserver
36 | }
37 |
38 | ss := s
39 | if len(ss) == 1 {
40 | // compatible with multi server env string: "a;b;c"
41 | ss = strings.Split(s[0], ";")
42 | }
43 |
44 | for _, srv := range ss {
45 | if err := verifyIP(srv); err != nil {
46 | return nil, err
47 | }
48 | }
49 |
50 | addrs := make(NamesrvAddr, 0)
51 | addrs = append(addrs, ss...)
52 | return addrs, nil
53 | }
54 |
55 | func (addr NamesrvAddr) Check() error {
56 | for _, srv := range addr {
57 | if err := verifyIP(srv); err != nil {
58 | return err
59 | }
60 | }
61 | return nil
62 | }
63 |
64 | var (
65 | httpPrefixRegex, _ = regexp.Compile("^(http|https)://")
66 | )
67 |
68 | func verifyIP(ip string) error {
69 | if httpPrefixRegex.MatchString(ip) {
70 | return nil
71 | }
72 | if strings.Contains(ip, ";") {
73 | return errors.ErrMultiIP
74 | }
75 | ipV4s := ipv4Regex.FindAllString(ip, -1)
76 | ipV6s := ipv6Regex.FindAllString(ip, -1)
77 |
78 | if len(ipV4s) == 0 && len(ipV6s) == 0 {
79 | return errors.ErrIllegalIP
80 | }
81 |
82 | if len(ipV4s) > 1 || len(ipV6s) > 1 {
83 | return errors.ErrMultiIP
84 | }
85 | return nil
86 | }
87 |
88 | type PanicHandler func(interface{})
89 |
90 | // primitive.DefaultPanicHandler = func(i interface{}) {
91 | // sentry.CaptureMessage(fmt.Sprintf("%+v", i), nil)
92 | // }
93 | var DefaultPanicHandler PanicHandler
94 |
95 | func WithRecover(fn func(), handlers ...PanicHandler) {
96 | defer func() {
97 | if len(handlers) == 0 {
98 | if DefaultPanicHandler != nil {
99 | handlers = append(handlers, DefaultPanicHandler)
100 | } else {
101 | handlers = append(handlers, func(interface{}) {})
102 | }
103 | }
104 | for _, handler := range handlers {
105 | if handler != nil {
106 | if err := recover(); err != nil {
107 | handler(err)
108 | }
109 | }
110 | }
111 | }()
112 |
113 | fn()
114 | }
115 |
116 | func Diff(origin, latest []string) bool {
117 | if len(origin) != len(latest) {
118 | return true
119 | }
120 |
121 | // check added
122 | originFilter := make(map[string]struct{}, len(origin))
123 | for _, srv := range origin {
124 | originFilter[srv] = struct{}{}
125 | }
126 |
127 | latestFilter := make(map[string]struct{}, len(latest))
128 | for _, srv := range latest {
129 | if _, ok := originFilter[srv]; !ok {
130 | return true // added
131 | }
132 | latestFilter[srv] = struct{}{}
133 | }
134 |
135 | // check delete
136 | for _, srv := range origin {
137 | if _, ok := latestFilter[srv]; !ok {
138 | return true // deleted
139 | }
140 | }
141 | return false
142 | }
143 |
--------------------------------------------------------------------------------
/primitive/base_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package primitive
19 |
20 | import (
21 | "fmt"
22 | "testing"
23 |
24 | "github.com/stretchr/testify/assert"
25 | )
26 |
27 | func TestVerifyIP(t *testing.T) {
28 | IPs := "127.0.0.1:9876"
29 | err := verifyIP(IPs)
30 | assert.Nil(t, err)
31 |
32 | IPs = "12.24.123.243:10911"
33 | err = verifyIP(IPs)
34 | assert.Nil(t, err)
35 |
36 | IPs = "xa2.0.0.1:9876"
37 | err = verifyIP(IPs)
38 | assert.Equal(t, "IP addr error", err.Error())
39 |
40 | IPs = "333.0.0.1:9876"
41 | err = verifyIP(IPs)
42 | assert.Equal(t, "IP addr error", err.Error())
43 |
44 | IPs = "127.0.0.1:9876;12.24.123.243:10911"
45 | err = verifyIP(IPs)
46 | assert.Equal(t, "multiple IP addr does not support", err.Error())
47 |
48 | IPs = "bdbd:bdbd:ff:1:1:2:3:4:8888"
49 | err = verifyIP(IPs)
50 | assert.Nil(t, err)
51 |
52 | IPs = "[bdbd:bdbd:ff:1:1:2:3:4]:8888"
53 | err = verifyIP(IPs)
54 | assert.Nil(t, err)
55 |
56 | IPs = "[bdbd:bdbd:ff:1:1:2:3:4]:8888;[bdbd:bdbd:ff:1:1:2:3:4]:8889"
57 | err = verifyIP(IPs)
58 | assert.Equal(t, "multiple IP addr does not support", err.Error())
59 | }
60 |
61 | func TestBase(t *testing.T) {
62 | a := []string{}
63 | b := []string{}
64 | assert.False(t, Diff(a, b))
65 |
66 | a = []string{"a"}
67 | b = []string{"a", "b"}
68 | assert.True(t, Diff(a, b))
69 |
70 | a = []string{"a", "b", "c"}
71 | b = []string{"c", "a", "b"}
72 | assert.False(t, Diff(a, b))
73 |
74 | a = []string{"b", "a"}
75 | b = []string{"a", "c"}
76 | assert.True(t, Diff(a, b))
77 | }
78 |
79 | func TestWithRecover(t *testing.T) {
80 | ass := assert.New(t)
81 |
82 | DefaultPanicHandler = nil
83 | ass.NotPanics(func() {
84 | WithRecover(func() {
85 | panic("test")
86 | })
87 | })
88 |
89 | DefaultPanicHandler = func(i interface{}) {
90 | fmt.Println("panic test")
91 | }
92 | ass.NotPanics(func() {
93 | WithRecover(func() {
94 | panic("test")
95 | })
96 | })
97 | }
98 |
--------------------------------------------------------------------------------
/primitive/errors.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package primitive
19 |
20 | import (
21 | "strconv"
22 | )
23 |
24 | type MQBrokerErr struct {
25 | ResponseCode int16
26 | ErrorMessage string
27 | }
28 |
29 | func (e MQBrokerErr) Error() string {
30 | return "CODE: " + strconv.Itoa(int(e.ResponseCode)) + " DESC: " + e.ErrorMessage
31 | }
32 |
33 | func NewRemotingErr(s string) error {
34 | return &RemotingErr{s: s}
35 | }
36 |
37 | type RemotingErr struct {
38 | s string
39 | }
40 |
41 | func (e *RemotingErr) Error() string {
42 | return e.s
43 | }
44 |
45 | func NewMQClientErr(code int16, msg string) error {
46 | return &MQClientErr{code: code, msg: msg}
47 | }
48 |
49 | type MQClientErr struct {
50 | code int16
51 | msg string
52 | }
53 |
54 | func (e MQClientErr) Error() string {
55 | return "CODE: " + strconv.Itoa(int(e.code)) + " DESC: " + e.msg
56 | }
57 |
58 | func IsRemotingErr(err error) bool {
59 | _, ok := err.(*RemotingErr)
60 | return ok
61 | }
62 |
--------------------------------------------------------------------------------
/primitive/interceptor.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package primitive
19 |
20 | import (
21 | "context"
22 | )
23 |
24 | // Invoker finish a message invoke on producer/consumer.
25 | type Invoker func(ctx context.Context, req, reply interface{}) error
26 |
27 | // Interceptor intercepts the invoke of a producer/consumer on messages.
28 | // In PushConsumer call, the req is []*MessageExt type and the reply is ConsumeResultHolder,
29 | // use type assert to get real type.
30 | type Interceptor func(ctx context.Context, req, reply interface{}, next Invoker) error
31 |
32 | func ChainInterceptors(interceptors ...Interceptor) Interceptor {
33 | if len(interceptors) == 0 {
34 | return nil
35 | }
36 | if len(interceptors) == 1 {
37 | return interceptors[0]
38 | }
39 | return func(ctx context.Context, req, reply interface{}, invoker Invoker) error {
40 | return interceptors[0](ctx, req, reply, getChainedInterceptor(interceptors, 0, invoker))
41 | }
42 | }
43 |
44 | func getChainedInterceptor(interceptors []Interceptor, cur int, finalInvoker Invoker) Invoker {
45 | if cur == len(interceptors)-1 {
46 | return finalInvoker
47 | }
48 | return func(ctx context.Context, req, reply interface{}) error {
49 | return interceptors[cur+1](ctx, req, reply, getChainedInterceptor(interceptors, cur+1, finalInvoker))
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/primitive/message_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package primitive
19 |
20 | import "testing"
21 |
22 | func TestMessageID(t *testing.T) {
23 | id := []byte("0AAF0895000078BF000000000009BB4A")
24 | msgID, err := UnmarshalMsgID(id)
25 | if err != nil {
26 | t.Fatalf("unmarshal msg id error, ms is: %s", err.Error())
27 | }
28 | if msgID.Addr != "10.175.8.149" {
29 | t.Fatalf("parse messageID %s error", id)
30 | }
31 | if msgID.Port != 30911 {
32 | t.Fatalf("parse messageID %s error", id)
33 | }
34 | if msgID.Offset != 637770 {
35 | t.Fatalf("parse messageID %s error", id)
36 | }
37 | t.Log(msgID)
38 | }
39 |
40 | func TestMessageKey(t *testing.T) {
41 | msg := &Message{}
42 | expected := "testKey"
43 | msg.WithKeys([]string{expected})
44 | actual := msg.GetKeys()
45 | if actual != expected {
46 | t.Fatalf("get message key error: expected is '%s', actual is '%s'", expected, actual)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/primitive/pool.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package primitive
19 |
20 | import (
21 | "bytes"
22 | "sync"
23 | )
24 |
25 | var headerPool = sync.Pool{}
26 | var bufferPool = sync.Pool{}
27 |
28 | func init() {
29 | headerPool.New = func() interface{} {
30 | b := make([]byte, 4)
31 | return &b
32 | }
33 | bufferPool.New = func() interface{} {
34 | return new(bytes.Buffer)
35 | }
36 | }
37 |
38 | func GetHeader() *[]byte {
39 | d := headerPool.Get().(*[]byte)
40 | //d = (d)[:0]
41 | return d
42 | }
43 |
44 | func BackHeader(d *[]byte) {
45 | headerPool.Put(d)
46 | }
47 |
48 | func GetBuffer() *bytes.Buffer {
49 | b := bufferPool.Get().(*bytes.Buffer)
50 | b.Reset()
51 | return b
52 | }
53 |
54 | func BackBuffer(b *bytes.Buffer) {
55 | b.Reset()
56 | bufferPool.Put(b)
57 | }
58 |
--------------------------------------------------------------------------------
/primitive/result.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package primitive
19 |
20 | import (
21 | "fmt"
22 | )
23 |
24 | // SendStatus of message
25 | type SendStatus int
26 |
27 | const (
28 | SendOK SendStatus = iota
29 | SendFlushDiskTimeout
30 | SendFlushSlaveTimeout
31 | SendSlaveNotAvailable
32 | SendUnknownError
33 |
34 | FlagCompressed = 0x1
35 | FlagBornHostV6 = 0x1 << 4
36 | FlagStoreHostV6 = 0x1 << 5
37 | MsgIdLength = 8 + 8
38 |
39 | propertySeparator = '\002'
40 | nameValueSeparator = '\001'
41 | )
42 |
43 | // SendResult RocketMQ send result
44 | type SendResult struct {
45 | Status SendStatus
46 | MsgID string
47 | MessageQueue *MessageQueue
48 | QueueOffset int64
49 | TransactionID string
50 | OffsetMsgID string
51 | RegionID string
52 | TraceOn bool
53 | }
54 |
55 | func NewSendResult() *SendResult {
56 | return &SendResult{Status: SendUnknownError}
57 | }
58 |
59 | // SendResult send message result to string(detail result)
60 | func (result *SendResult) String() string {
61 | mq := "nil"
62 | if result.MessageQueue != nil {
63 | mq = result.MessageQueue.String()
64 | }
65 | return fmt.Sprintf("SendResult [sendStatus=%d, msgIds=%s, offsetMsgId=%s, queueOffset=%d, messageQueue=%s]",
66 | result.Status, result.MsgID, result.OffsetMsgID, result.QueueOffset, mq)
67 | }
68 |
69 | // TransactionSendResult RocketMQ send result
70 | type TransactionSendResult struct {
71 | *SendResult
72 | State LocalTransactionState
73 | }
74 |
75 | // PullStatus pull Status
76 | type PullStatus int
77 |
78 | // predefined pull Status
79 | const (
80 | PullFound PullStatus = iota
81 | PullNoNewMsg
82 | PullNoMsgMatched
83 | PullOffsetIllegal
84 | PullBrokerTimeout
85 | )
86 |
87 | // PullResult the pull result
88 | type PullResult struct {
89 | NextBeginOffset int64
90 | MinOffset int64
91 | MaxOffset int64
92 | Status PullStatus
93 | SuggestWhichBrokerId int64
94 |
95 | // messageExts message info
96 | messageExts []*MessageExt
97 | //
98 | body []byte
99 | }
100 |
101 | func (result *PullResult) GetMessageExts() []*MessageExt {
102 | return result.messageExts
103 | }
104 |
105 | func (result *PullResult) SetMessageExts(msgExts []*MessageExt) {
106 | result.messageExts = msgExts
107 | }
108 |
109 | func (result *PullResult) GetMessages() []*Message {
110 | if len(result.messageExts) == 0 {
111 | return make([]*Message, 0)
112 | }
113 | return toMessages(result.messageExts)
114 | }
115 |
116 | func (result *PullResult) SetBody(data []byte) {
117 | result.body = data
118 | }
119 |
120 | func (result *PullResult) GetBody() []byte {
121 | return result.body
122 | }
123 |
124 | func (result *PullResult) String() string {
125 | return ""
126 | }
127 |
128 | func toMessages(messageExts []*MessageExt) []*Message {
129 | msgs := make([]*Message, 0)
130 | for _, messageExt := range messageExts {
131 | msgs = append(msgs, &messageExt.Message)
132 | }
133 | return msgs
134 | }
135 |
--------------------------------------------------------------------------------
/primitive/result_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package primitive
19 |
20 | import (
21 | "testing"
22 |
23 | . "github.com/smartystreets/goconvey/convey"
24 | "github.com/stretchr/testify/assert"
25 | )
26 |
27 | func TestProperties(t *testing.T) {
28 | msg1 := NewMessage("test", nil)
29 | msg1.properties = map[string]string{
30 | "k1": "v1",
31 | "k2": "v2",
32 | }
33 | str := msg1.MarshallProperties()
34 | msg2 := NewMessage("test", nil)
35 | msg2.UnmarshalProperties([]byte(str))
36 | assert.Equal(t, msg1.properties, msg2.properties)
37 | }
38 |
39 | func TestCreateMessageId(t *testing.T) {
40 | Convey("MessageId gen", t, func() {
41 | b := []byte{10, 93, 233, 58}
42 | port := int32(10911)
43 | offset := int64(4391252)
44 | id := CreateMessageId(b, port, offset)
45 | Convey("generated messageId should be equal to expected", func() {
46 | assert.Equal(t, "0A5DE93A00002A9F0000000000430154", id)
47 | })
48 |
49 | b2 := []byte("127.0.0.1")
50 | port2 := int32(11)
51 | offset2 := int64(12)
52 | id2 := CreateMessageId(b2, port2, offset2)
53 | Convey("new generated messageId should be equal to expected", func() {
54 | assert.Equal(t, "3132372E302E302E310000000B000000000000000C", id2)
55 | })
56 |
57 | Convey("ex-generated messageId should not change", func() {
58 | assert.Equal(t, "0A5DE93A00002A9F0000000000430154", id)
59 | })
60 | })
61 |
62 | }
63 |
64 | func TestGetProperties(t *testing.T) {
65 | msg1 := NewMessage("test", nil)
66 | msg1.properties = map[string]string{
67 | "k1": "v1",
68 | "k2": "v2",
69 | }
70 | assert.Equal(t, msg1.GetProperties(), map[string]string{
71 | "k1": "v1",
72 | "k2": "v2",
73 | })
74 | }
75 |
--------------------------------------------------------------------------------
/primitive/trace.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package primitive
19 |
20 | // config for message trace.
21 | type TraceConfig struct {
22 | TraceTopic string
23 | GroupName string
24 | Access AccessChannel
25 | NamesrvAddrs []string
26 | Resolver NsResolver
27 | Credentials // acl config for trace. omit if acl is closed on broker.
28 | }
29 |
--------------------------------------------------------------------------------
/producer/interceptor.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | /**
19 | * builtin interceptor
20 | */
21 | package producer
22 |
23 | import (
24 | "context"
25 | "fmt"
26 | "time"
27 |
28 | "github.com/apache/rocketmq-client-go/v2/internal"
29 | "github.com/apache/rocketmq-client-go/v2/internal/utils"
30 | "github.com/apache/rocketmq-client-go/v2/primitive"
31 | )
32 |
33 | // WithTrace support rocketmq trace: https://github.com/apache/rocketmq/wiki/RIP-6-Message-Trace.
34 | func WithTrace(traceCfg *primitive.TraceConfig) Option {
35 | return func(options *producerOptions) {
36 | dispatcher := internal.NewTraceDispatcher(traceCfg)
37 | options.TraceDispatcher = dispatcher
38 | ori := options.Interceptors
39 | options.Interceptors = make([]primitive.Interceptor, 0)
40 | options.Interceptors = append(options.Interceptors, newTraceInterceptor(dispatcher))
41 | options.Interceptors = append(options.Interceptors, ori...)
42 | }
43 | }
44 |
45 | func newTraceInterceptor(dispatcher internal.TraceDispatcher) primitive.Interceptor {
46 | if dispatcher != nil {
47 | dispatcher.Start()
48 | }
49 |
50 | return func(ctx context.Context, req, reply interface{}, next primitive.Invoker) error {
51 | if dispatcher == nil {
52 | return fmt.Errorf("GetOrNewRocketMQClient faild")
53 | }
54 | beginT := time.Now()
55 | producerCtx, ok := primitive.GetProducerCtx(ctx)
56 | if !ok {
57 | return fmt.Errorf("ProducerCtx Not Exist")
58 | }
59 | err := next(ctx, req, reply)
60 |
61 | if producerCtx.Message.Topic == dispatcher.GetTraceTopicName() {
62 | return err
63 | }
64 |
65 | // SendOneway && SendAsync has no reply.
66 | if reply == nil {
67 | return err
68 | }
69 |
70 | result := reply.(*primitive.SendResult)
71 | if result.RegionID == "" || !result.TraceOn {
72 | return err
73 | }
74 |
75 | sendSuccess := result.Status == primitive.SendOK
76 | costT := time.Since(beginT).Nanoseconds() / int64(time.Millisecond)
77 | storeT := beginT.UnixNano()/int64(time.Millisecond) + costT/2
78 |
79 | traceBean := internal.TraceBean{
80 | Topic: producerCtx.Message.Topic,
81 | Tags: producerCtx.Message.GetTags(),
82 | Keys: producerCtx.Message.GetKeys(),
83 | StoreHost: producerCtx.BrokerAddr,
84 | ClientHost: utils.LocalIP,
85 | BodyLength: len(producerCtx.Message.Body),
86 | MsgType: producerCtx.MsgType,
87 | MsgId: result.MsgID,
88 | OffsetMsgId: result.OffsetMsgID,
89 | StoreTime: storeT,
90 | }
91 |
92 | traceCtx := internal.TraceContext{
93 | RequestId: primitive.CreateUniqID(), // set id
94 | TimeStamp: time.Now().UnixNano() / int64(time.Millisecond),
95 |
96 | TraceType: internal.Pub,
97 | GroupName: producerCtx.ProducerGroup,
98 | RegionId: result.RegionID,
99 | TraceBeans: []internal.TraceBean{traceBean},
100 | CostTime: costT,
101 | IsSuccess: sendSuccess,
102 | }
103 | dispatcher.Append(traceCtx)
104 | return err
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/producer/option_test.go:
--------------------------------------------------------------------------------
1 | package producer
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func getFieldString(obj interface{}, field string) string {
10 | v := reflect.Indirect(reflect.ValueOf(obj))
11 | return v.FieldByNameFunc(func(n string) bool {
12 | return n == field
13 | }).String()
14 | }
15 |
16 | func TestWithRemotingTimeout(t *testing.T) {
17 | opt := defaultProducerOptions()
18 | WithRemotingTimeout(3*time.Second, 4*time.Second, 5*time.Second)(&opt)
19 | if timeout := opt.RemotingClientConfig.ConnectionTimeout; timeout != 3*time.Second {
20 | t.Errorf("consumer option WithRemotingTimeout connectionTimeout. want:%s, got=%s", 3*time.Second, timeout)
21 | }
22 | if timeout := opt.RemotingClientConfig.ReadTimeout; timeout != 4*time.Second {
23 | t.Errorf("consumer option WithRemotingTimeout readTimeout. want:%s, got=%s", 4*time.Second, timeout)
24 | }
25 | if timeout := opt.RemotingClientConfig.WriteTimeout; timeout != 5*time.Second {
26 | t.Errorf("consumer option WithRemotingTimeout writeTimeout. want:%s, got=%s", 5*time.Second, timeout)
27 | }
28 | }
29 |
30 | func TestWithUnitName(t *testing.T) {
31 | opt := defaultProducerOptions()
32 | unitName := "unsh"
33 | WithUnitName(unitName)(&opt)
34 | if opt.UnitName != unitName {
35 | t.Errorf("producer option WithUnitName. want:%s, got=%s", unitName, opt.UnitName)
36 | }
37 | }
38 |
39 | func TestWithNameServerDomain(t *testing.T) {
40 | opt := defaultProducerOptions()
41 | nameServerAddr := "http://127.0.0.1:8080/nameserver/addr"
42 | WithNameServerDomain(nameServerAddr)(&opt)
43 | domainStr := getFieldString(opt.Resolver, "domain")
44 | if domainStr != nameServerAddr {
45 | t.Errorf("producer option WithUnitName. want:%s, got=%s", nameServerAddr, domainStr)
46 | }
47 | }
48 |
49 | func TestWithNameServerDomainAndUnitName(t *testing.T) {
50 | unitName := "unsh"
51 | // test with two different orders
52 | t.Run("WithNameServerDomain & WithUnitName", func(t *testing.T) {
53 | addr := "http://127.0.0.1:8080/nameserver/addr"
54 | opt := defaultProducerOptions()
55 | WithNameServerDomain(addr)(&opt)
56 | WithUnitName(unitName)(&opt)
57 |
58 | domainStr := getFieldString(opt.Resolver, "domain")
59 | expectedAddr := "http://127.0.0.1:8080/nameserver/addr-unsh?nofix=1"
60 | if domainStr != expectedAddr {
61 | t.Errorf("producer option WithNameServerDomain & WithUnitName. want:%s, got=%s", expectedAddr, domainStr)
62 | }
63 | })
64 |
65 | t.Run("WithUnitName & WithNameServerDomain", func(t *testing.T) {
66 | addr := "http://127.0.0.1:8080/nameserver/addr"
67 | opt := defaultProducerOptions()
68 | WithUnitName(unitName)(&opt)
69 | WithNameServerDomain(addr)(&opt)
70 |
71 | domainStr := getFieldString(opt.Resolver, "domain")
72 | expectedAddr := "http://127.0.0.1:8080/nameserver/addr-unsh?nofix=1"
73 | if domainStr != expectedAddr {
74 | t.Errorf("producer option WithUnitName & WithNameServerDomain. want:%s, got=%s", expectedAddr, domainStr)
75 | }
76 | })
77 |
78 | // test with two different orders - name server with query string
79 | t.Run("WithNameServerDomain & WithUnitName", func(t *testing.T) {
80 | addr := "http://127.0.0.1:8080/nameserver/addr?labels=abc"
81 | opt := defaultProducerOptions()
82 | WithNameServerDomain(addr)(&opt)
83 | WithUnitName(unitName)(&opt)
84 |
85 | domainStr := getFieldString(opt.Resolver, "domain")
86 | expectedAddr := "http://127.0.0.1:8080/nameserver/addr-unsh?nofix=1&labels=abc"
87 | if domainStr != expectedAddr {
88 | t.Errorf("producer option WithNameServerDomain & WithUnitName. want:%s, got=%s", expectedAddr, domainStr)
89 | }
90 | })
91 |
92 | t.Run("WithUnitName & WithNameServerDomain", func(t *testing.T) {
93 | addr := "http://127.0.0.1:8080/nameserver/addr?labels=abc"
94 | opt := defaultProducerOptions()
95 | WithUnitName(unitName)(&opt)
96 | WithNameServerDomain(addr)(&opt)
97 |
98 | domainStr := getFieldString(opt.Resolver, "domain")
99 | expectedAddr := "http://127.0.0.1:8080/nameserver/addr-unsh?nofix=1&labels=abc"
100 | if domainStr != expectedAddr {
101 | t.Errorf("producer option WithUnitName & WithNameServerDomain. want:%s, got=%s", expectedAddr, domainStr)
102 | }
103 | })
104 | }
105 |
--------------------------------------------------------------------------------
/producer/selector.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package producer
19 |
20 | import (
21 | "hash/fnv"
22 | "math/rand"
23 | "sync"
24 | "time"
25 |
26 | "github.com/apache/rocketmq-client-go/v2/primitive"
27 | )
28 |
29 | type QueueSelector interface {
30 | Select(msg *primitive.Message, mqs []*primitive.MessageQueue, lastBrokerName string) *primitive.MessageQueue
31 | }
32 |
33 | // manualQueueSelector use the queue manually set in the provided Message's QueueID field as the queue to send.
34 | type manualQueueSelector struct{}
35 |
36 | func NewManualQueueSelector() QueueSelector {
37 | return new(manualQueueSelector)
38 | }
39 |
40 | func (manualQueueSelector) Select(message *primitive.Message, queues []*primitive.MessageQueue, lastBrokerName string) *primitive.MessageQueue {
41 | return message.Queue
42 | }
43 |
44 | // randomQueueSelector choose a random queue each time.
45 | type randomQueueSelector struct {
46 | mux sync.Mutex
47 | rander *rand.Rand
48 | }
49 |
50 | func NewRandomQueueSelector() QueueSelector {
51 | s := new(randomQueueSelector)
52 | s.rander = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
53 | return s
54 | }
55 |
56 | func (r *randomQueueSelector) Select(message *primitive.Message, queues []*primitive.MessageQueue, lastBrokerName string) *primitive.MessageQueue {
57 | r.mux.Lock()
58 | i := r.rander.Intn(len(queues))
59 | r.mux.Unlock()
60 | return queues[i]
61 | }
62 |
63 | // roundRobinQueueSelector choose the queue by roundRobin.
64 | type roundRobinQueueSelector struct {
65 | sync.Locker
66 | indexer map[string]*uint32
67 | }
68 |
69 | func NewRoundRobinQueueSelector() QueueSelector {
70 | s := &roundRobinQueueSelector{
71 | Locker: new(sync.Mutex),
72 | indexer: map[string]*uint32{},
73 | }
74 | return s
75 | }
76 |
77 | func (r *roundRobinQueueSelector) Select(message *primitive.Message, queues []*primitive.MessageQueue, lastBrokerName string) *primitive.MessageQueue {
78 | t := message.Topic
79 |
80 | r.Lock()
81 | defer r.Unlock()
82 | if lastBrokerName != "" {
83 | for i := 0; i < len(queues); i++ {
84 | idx, exist := r.indexer[t]
85 | if !exist {
86 | var v uint32 = 0
87 | idx = &v
88 | r.indexer[t] = idx
89 | }
90 | *idx++
91 | qIndex := *idx % uint32(len(queues))
92 | if queues[qIndex].BrokerName != lastBrokerName {
93 | return queues[qIndex]
94 | }
95 | }
96 | }
97 | return r.selectOneMessageQueue(t, queues)
98 | }
99 |
100 | func (r *roundRobinQueueSelector) selectOneMessageQueue(t string, queues []*primitive.MessageQueue) *primitive.MessageQueue {
101 | var idx *uint32
102 |
103 | idx, exist := r.indexer[t]
104 | if !exist {
105 | var v uint32 = 0
106 | idx = &v
107 | r.indexer[t] = idx
108 | }
109 | *idx++
110 |
111 | qIndex := *idx % uint32(len(queues))
112 | return queues[qIndex]
113 | }
114 |
115 | type hashQueueSelector struct {
116 | random QueueSelector
117 | }
118 |
119 | func NewHashQueueSelector() QueueSelector {
120 | return &hashQueueSelector{
121 | random: NewRandomQueueSelector(),
122 | }
123 | }
124 |
125 | // hashQueueSelector choose the queue by hash if message having sharding key, otherwise choose queue by random instead.
126 | func (h *hashQueueSelector) Select(message *primitive.Message, queues []*primitive.MessageQueue, lastBrokerName string) *primitive.MessageQueue {
127 | key := message.GetShardingKey()
128 | if len(key) == 0 {
129 | return h.random.Select(message, queues, lastBrokerName)
130 | }
131 |
132 | hasher := fnv.New32a()
133 | _, err := hasher.Write([]byte(key))
134 | if err != nil {
135 | return nil
136 | }
137 | queueId := int(hasher.Sum32()) % len(queues)
138 | if queueId < 0 {
139 | queueId = -queueId
140 | }
141 | return queues[queueId]
142 | }
143 |
--------------------------------------------------------------------------------
/producer/selector_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one or more
3 | contributor license agreements. See the NOTICE file distributed with
4 | this work for additional information regarding copyright ownership.
5 | The ASF licenses this file to You under the Apache License, Version 2.0
6 | (the "License"); you may not use this file except in compliance with
7 | the License. You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package producer
19 |
20 | import (
21 | "testing"
22 |
23 | "github.com/stretchr/testify/assert"
24 |
25 | "github.com/apache/rocketmq-client-go/v2/primitive"
26 | )
27 |
28 | func TestRoundRobin(t *testing.T) {
29 | queues := make([]*primitive.MessageQueue, 0)
30 | for i := 0; i < 10; i++ {
31 | queues = append(queues, &primitive.MessageQueue{
32 | QueueId: i,
33 | })
34 | }
35 | s := NewRoundRobinQueueSelector()
36 |
37 | m := &primitive.Message{
38 | Topic: "test",
39 | }
40 | mrr := &primitive.Message{
41 | Topic: "rr",
42 | }
43 | for i := 0; i < 100; i++ {
44 | q := s.Select(m, queues, "")
45 | expected := (i + 1) % len(queues)
46 | assert.Equal(t, queues[expected], q, "i: %d", i)
47 |
48 | qrr := s.Select(mrr, queues, "")
49 | expected = (i + 1) % len(queues)
50 | assert.Equal(t, queues[expected], qrr, "i: %d", i)
51 | }
52 | }
53 |
54 | func TestRoundRobinRetry(t *testing.T) {
55 | queues := make([]*primitive.MessageQueue, 0)
56 | brokerA := "brokerA"
57 | brokerB := "brokerB"
58 | for i := 0; i < 5; i++ {
59 | queues = append(queues, &primitive.MessageQueue{
60 | QueueId: i,
61 | BrokerName: brokerA,
62 | })
63 | queues = append(queues, &primitive.MessageQueue{
64 | QueueId: i,
65 | BrokerName: brokerB,
66 | })
67 | }
68 | s := NewRoundRobinQueueSelector()
69 |
70 | m := &primitive.Message{
71 | Topic: "test",
72 | }
73 | for i := 0; i < 100; i++ {
74 | q := s.Select(m, queues, brokerA)
75 | assert.Equal(t, brokerB, q.BrokerName)
76 | }
77 | }
78 |
79 | func TestHashQueueSelector(t *testing.T) {
80 | queues := make([]*primitive.MessageQueue, 0)
81 | for i := 0; i < 10; i++ {
82 | queues = append(queues, &primitive.MessageQueue{
83 | QueueId: i,
84 | })
85 | }
86 |
87 | s := NewHashQueueSelector()
88 |
89 | m1 := &primitive.Message{
90 | Topic: "test",
91 | Body: []byte("one message"),
92 | }
93 | m1.WithShardingKey("same_key")
94 | q1 := s.Select(m1, queues, "")
95 |
96 | m2 := &primitive.Message{
97 | Topic: "test",
98 | Body: []byte("another message"),
99 | }
100 | m2.WithShardingKey("same_key")
101 | q2 := s.Select(m2, queues, "")
102 | assert.Equal(t, *q1, *q2)
103 | }
104 |
--------------------------------------------------------------------------------