├── .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 | [![Build Status](https://app.travis-ci.com/apache/rocketmq-client-go.svg?branch=master)](https://app.travis-ci.com/apache/rocketmq-client-go) 3 | [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 4 | [![Code Scanning](https://github.com/apache/rocketmq-client-go/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/apache/rocketmq-client-go/actions/workflows/codeql-analysis.yml) 5 | [![Tests](https://github.com/apache/rocketmq-client-go/actions/workflows/tests.yaml/badge.svg)](https://github.com/apache/rocketmq-client-go/actions/workflows/tests.yaml) 6 | [![CodeCov](https://codecov.io/gh/apache/rocketmq-client-go/branch/master/graph/badge.svg)](https://codecov.io/gh/apache/rocketmq-client-go) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/apache/rocketmq-client-go)](https://goreportcard.com/report/github.com/apache/rocketmq-client-go) 8 | [![GoDoc](https://img.shields.io/badge/Godoc-reference-blue.svg)](https://godoc.org/github.com/apache/rocketmq-client-go) 9 | [![GitHub release](https://img.shields.io/github/release-date-pre/apache/rocketmq-client-go)](https://github.com/apache/rocketmq-client-go/releases) 10 | [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/apache/rocketmq-client-go.svg)](http://isitmaintained.com/project/apache/rocketmq-client-go "Average time to resolve an issue") 11 | [![Percentage of issues still open](http://isitmaintained.com/badge/open/apache/rocketmq-client-go.svg)](http://isitmaintained.com/project/apache/rocketmq-client-go "Percentage of issues still open") 12 | [![Twitter Follow](https://img.shields.io/twitter/follow/ApacheRocketMQ?style=social)](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 | ![client-design](../images/client-design.png) 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 | --------------------------------------------------------------------------------