diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/Guardfile b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/Guardfile new file mode 100644 index 00000000..bdd4d729 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/Guardfile @@ -0,0 +1,20 @@ +require 'asciidoctor' +require 'erb' + +guard 'shell' do + watch(/.*\.adoc$/) {|m| + Asciidoctor.render_file('index.adoc', \ + :in_place => true, \ + :safe => Asciidoctor::SafeMode::UNSAFE, \ + :attributes=> { \ + 'source-highlighter' => 'prettify', \ + 'icons' => 'font', \ + 'linkcss'=> 'true', \ + 'copycss' => 'true', \ + 'doctype' => 'book'}) + } +end + +guard 'livereload' do + watch(%r{^.+\.(css|js|html)$}) +end diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/css/highlight.css b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/css/highlight.css new file mode 100644 index 00000000..3850f8b9 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/css/highlight.css @@ -0,0 +1,35 @@ +/* + code highlight CSS resemblign the Eclipse IDE default color schema + @author Costin Leau +*/ + +.hl-keyword { + color: #7F0055; + font-weight: bold; +} + +.hl-comment { + color: #3F5F5F; + font-style: italic; +} + +.hl-multiline-comment { + color: #3F5FBF; + font-style: italic; +} + +.hl-tag { + color: #3F7F7F; +} + +.hl-attribute { + color: #7F007F; +} + +.hl-value { + color: #2A00FF; +} + +.hl-string { + color: #2A00FF; +} \ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/css/manual-multipage.css b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/css/manual-multipage.css new file mode 100644 index 00000000..b790654b --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/css/manual-multipage.css @@ -0,0 +1,9 @@ +@IMPORT url("manual.css"); + +body.firstpage { + background: url("../images/background.png") no-repeat center top; +} + +div.part h1 { + border-top: none; +} diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/css/manual-singlepage.css b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/css/manual-singlepage.css new file mode 100644 index 00000000..303192a8 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/css/manual-singlepage.css @@ -0,0 +1,6 @@ +@IMPORT url("manual.css"); + +body { + background: url("../images/background.png") no-repeat center top; +} + diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/css/manual.css b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/css/manual.css new file mode 100644 index 00000000..20cf07da --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/css/manual.css @@ -0,0 +1,342 @@ +@IMPORT url("highlight.css"); + +html { + padding: 0pt; + margin: 0pt; +} + +body { + color: #333333; + margin: 15px 30px; + font-family: Helvetica, Arial, Freesans, Clean, Sans-serif; + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} + +code { + font-size: 16px; + font-family: Consolas, "Liberation Mono", Courier, monospace; +} + +:not(a) > code { + color: #6D180B; +} + +:not(pre) > code { + background-color: #F2F2F2; + border: 1px solid #CCCCCC; + border-radius: 4px; + padding: 1px 3px 0; + text-shadow: none; + white-space: nowrap; +} + +body > *:first-child { + margin-top: 0 !important; +} + +div { + margin: 0pt; +} + +hr { + border: 1px solid #CCCCCC; + background: #CCCCCC; +} + +h1, h2, h3, h4, h5, h6 { + color: #000000; + cursor: text; + font-weight: bold; + margin: 30px 0 10px; + padding: 0; +} + +h1, h2, h3 { + margin: 40px 0 10px; +} + +h1 { + margin: 70px 0 30px; + padding-top: 20px; +} + +div.part h1 { + border-top: 1px dotted #CCCCCC; +} + +h1, h1 code { + font-size: 32px; +} + +h2, h2 code { + font-size: 24px; +} + +h3, h3 code { + font-size: 20px; +} + +h4, h1 code, h5, h5 code, h6, h6 code { + font-size: 18px; +} + +div.book, div.chapter, div.appendix, div.part, div.preface { + min-width: 300px; + max-width: 1200px; + margin: 0 auto; +} + +p.releaseinfo { + font-weight: bold; + margin-bottom: 40px; + margin-top: 40px; +} + +div.authorgroup { + line-height: 1; +} + +p.copyright { + line-height: 1; + margin-bottom: -5px; +} + +.legalnotice p { + font-style: italic; + font-size: 14px; + line-height: 1; +} + +div.titlepage + p, div.titlepage + p { + margin-top: 0; +} + +pre { + line-height: 1.0; + color: black; +} + +a { + color: #4183C4; + text-decoration: none; +} + +p { + margin: 15px 0; + text-align: left; +} + +ul, ol { + padding-left: 30px; +} + +li p { + margin: 0; +} + +div.table { + margin: 1em; + padding: 0.5em; + text-align: center; +} + +div.table table, div.informaltable table { + display: table; + width: 100%; +} + +div.table td { + padding-left: 7px; + padding-right: 7px; +} + +.sidebar { + line-height: 1.4; + padding: 0 20px; + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; +} + +.sidebar p.title { + color: #6D180B; +} + +pre.programlisting, pre.screen { + font-size: 15px; + padding: 6px 10px; + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; + clear: both; + overflow: auto; + line-height: 1.4; + font-family: Consolas, "Liberation Mono", Courier, monospace; +} + +table { + border-collapse: collapse; + border-spacing: 0; + border: 1px solid #DDDDDD !important; + border-radius: 4px !important; + border-collapse: separate !important; + line-height: 1.6; +} + +table thead { + background: #F5F5F5; +} + +table tr { + border: none; + border-bottom: none; +} + +table th { + font-weight: bold; +} + +table th, table td { + border: none !important; + padding: 6px 13px; +} + +table tr:nth-child(2n) { + background-color: #F8F8F8; +} + +td p { + margin: 0 0 15px 0; +} + +div.table-contents td p { + margin: 0; +} + +div.important *, div.note *, div.tip *, div.warning *, div.navheader *, div.navfooter *, div.calloutlist * { + border: none !important; + background: none !important; + margin: 0; +} + +div.important p, div.note p, div.tip p, div.warning p { + color: #6F6F6F; + line-height: 1.6; +} + +div.important code, div.note code, div.tip code, div.warning code { + background-color: #F2F2F2 !important; + border: 1px solid #CCCCCC !important; + border-radius: 4px !important; + padding: 1px 3px 0 !important; + text-shadow: none !important; + white-space: nowrap !important; +} + +.note th, .tip th, .warning th { + display: none; +} + +.note tr:first-child td, .tip tr:first-child td, .warning tr:first-child td { + border-right: 1px solid #CCCCCC !important; + padding-top: 10px; +} + +div.calloutlist p, div.calloutlist td { + padding: 0; + margin: 0; +} + +div.calloutlist > table > tbody > tr > td:first-child { + padding-left: 10px; + width: 30px !important; +} + +div.important, div.note, div.tip, div.warning { + margin-left: 0px !important; + margin-right: 20px !important; + margin-top: 20px; + margin-bottom: 20px; + padding-top: 10px; + padding-bottom: 10px; +} + +div.toc { + line-height: 1.2; +} + +dl, dt { + margin-top: 1px; + margin-bottom: 0; +} + +div.toc > dl > dt { + font-size: 32px; + font-weight: bold; + margin: 30px 0 10px 0; + display: block; +} + +div.toc > dl > dd > dl > dt { + font-size: 24px; + font-weight: bold; + margin: 20px 0 10px 0; + display: block; +} + +div.toc > dl > dd > dl > dd > dl > dt { + font-weight: bold; + font-size: 20px; + margin: 10px 0 0 0; +} + +tbody.footnotes * { + border: none !important; +} + +div.footnote p { + margin: 0; + line-height: 1; +} + +div.footnote p sup { + margin-right: 6px; + vertical-align: middle; +} + +div.navheader { + border-bottom: 1px solid #CCCCCC; +} + +div.navfooter { + border-top: 1px solid #CCCCCC; +} + +.title { + margin-left: -1em; + padding-left: 1em; +} + +.title > a { + position: absolute; + visibility: hidden; + display: block; + font-size: 0.85em; + margin-top: 0.05em; + margin-left: -1em; + vertical-align: text-top; + color: black; +} + +.title > a:before { + content: "\00A7"; +} + +.title:hover > a, .title > a:hover, .title:hover > a:hover { + visibility: visible; +} + +.title:focus > a, .title > a:focus, .title:focus > a:focus { + outline: 0; +} diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/ghpages.sh b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/ghpages.sh new file mode 100644 index 00000000..2562c717 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/ghpages.sh @@ -0,0 +1,330 @@ +#!/bin/bash -x + +set -e + +# Set default props like MAVEN_PATH, ROOT_FOLDER etc. +function set_default_props() { + # The script should be executed from the root folder + ROOT_FOLDER=`pwd` + echo "Current folder is ${ROOT_FOLDER}" + + if [[ ! -e "${ROOT_FOLDER}/.git" ]]; then + echo "You're not in the root folder of the project!" + exit 1 + fi + + # Prop that will let commit the changes + COMMIT_CHANGES="no" + MAVEN_PATH=${MAVEN_PATH:-} + echo "Path to Maven is [${MAVEN_PATH}]" + REPO_NAME=${PWD##*/} + echo "Repo name is [${REPO_NAME}]" + SPRING_CLOUD_STATIC_REPO=${SPRING_CLOUD_STATIC_REPO:-git@github.com:spring-cloud/spring-cloud-static.git} + echo "Spring Cloud Static repo is [${SPRING_CLOUD_STATIC_REPO}" +} + +# Check if gh-pages exists and docs have been built +function check_if_anything_to_sync() { + git remote set-url --push origin `git config remote.origin.url | sed -e 's/^git:/https:/'` + + if ! (git remote set-branches --add origin gh-pages && git fetch -q); then + echo "No gh-pages, so not syncing" + exit 0 + fi + + if ! [ -d docs/target/generated-docs ] && ! [ "${BUILD}" == "yes" ]; then + echo "No gh-pages sources in docs/target/generated-docs, so not syncing" + exit 0 + fi +} + +function retrieve_current_branch() { + # Code getting the name of the current branch. For master we want to publish as we did until now + # https://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch + # If there is a branch already passed will reuse it - otherwise will try to find it + CURRENT_BRANCH=${BRANCH} + if [[ -z "${CURRENT_BRANCH}" ]] ; then + CURRENT_BRANCH=$(git symbolic-ref -q HEAD) + CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/} + CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD} + fi + echo "Current branch is [${CURRENT_BRANCH}]" + git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script" +} + +# Switches to the provided value of the release version. We always prefix it with `v` +function switch_to_tag() { + git checkout v${VERSION} +} + +# Build the docs if switch is on +function build_docs_if_applicable() { + if [[ "${BUILD}" == "yes" ]] ; then + ./mvnw clean install -P docs -pl docs -DskipTests + fi +} + +# Get the name of the `docs.main` property +# Get whitelisted branches - assumes that a `docs` module is available under `docs` profile +function retrieve_doc_properties() { + MAIN_ADOC_VALUE=$("${MAVEN_PATH}"mvn -q \ + -Dexec.executable="echo" \ + -Dexec.args='${docs.main}' \ + --non-recursive \ + org.codehaus.mojo:exec-maven-plugin:1.3.1:exec) + echo "Extracted 'main.adoc' from Maven build [${MAIN_ADOC_VALUE}]" + + + WHITELIST_PROPERTY=${WHITELIST_PROPERTY:-"docs.whitelisted.branches"} + WHITELISTED_BRANCHES_VALUE=$("${MAVEN_PATH}"mvn -q \ + -Dexec.executable="echo" \ + -Dexec.args="\${${WHITELIST_PROPERTY}}" \ + org.codehaus.mojo:exec-maven-plugin:1.3.1:exec \ + -P docs \ + -pl docs) + echo "Extracted '${WHITELIST_PROPERTY}' from Maven build [${WHITELISTED_BRANCHES_VALUE}]" +} + +# Stash any outstanding changes +function stash_changes() { + git diff-index --quiet HEAD && dirty=$? || (echo "Failed to check if the current repo is dirty. Assuming that it is." && dirty="1") + if [ "$dirty" != "0" ]; then git stash; fi +} + +# Switch to gh-pages branch to sync it with current branch +function add_docs_from_target() { + local DESTINATION_REPO_FOLDER + if [[ -z "${DESTINATION}" && -z "${CLONE}" ]] ; then + DESTINATION_REPO_FOLDER=${ROOT_FOLDER} + elif [[ "${CLONE}" == "yes" ]]; then + mkdir -p ${ROOT_FOLDER}/target + local clonedStatic=${ROOT_FOLDER}/target/spring-cloud-static + if [[ ! -e "${clonedStatic}/.git" ]]; then + echo "Cloning Spring Cloud Static to target" + git clone ${SPRING_CLOUD_STATIC_REPO} ${clonedStatic} && git checkout gh-pages + else + echo "Spring Cloud Static already cloned - will pull changes" + cd ${clonedStatic} && git checkout gh-pages && git pull origin gh-pages + fi + DESTINATION_REPO_FOLDER=${clonedStatic}/${REPO_NAME} + mkdir -p ${DESTINATION_REPO_FOLDER} + else + if [[ ! -e "${DESTINATION}/.git" ]]; then + echo "[${DESTINATION}] is not a git repository" + exit 1 + fi + DESTINATION_REPO_FOLDER=${DESTINATION}/${REPO_NAME} + mkdir -p ${DESTINATION_REPO_FOLDER} + echo "Destination was provided [${DESTINATION}]" + fi + cd ${DESTINATION_REPO_FOLDER} + git checkout gh-pages + git pull origin gh-pages + + # Add git branches + ################################################################### + if [[ -z "${VERSION}" ]] ; then + copy_docs_for_current_version + else + copy_docs_for_provided_version + fi + commit_changes_if_applicable +} + + +# Copies the docs by using the retrieved properties from Maven build +function copy_docs_for_current_version() { + if [[ "${CURRENT_BRANCH}" == "master" ]] ; then + echo -e "Current branch is master - will copy the current docs only to the root folder" + for f in docs/target/generated-docs/*; do + file=${f#docs/target/generated-docs/*} + if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then + # Not ignored... + cp -rf $f ${ROOT_FOLDER}/ + git add -A ${ROOT_FOLDER}/$file + fi + done + COMMIT_CHANGES="yes" + else + echo -e "Current branch is [${CURRENT_BRANCH}]" + # https://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin + if [[ ",${WHITELISTED_BRANCHES_VALUE}," = *",${CURRENT_BRANCH},"* ]] ; then + mkdir -p ${ROOT_FOLDER}/${CURRENT_BRANCH} + echo -e "Branch [${CURRENT_BRANCH}] is whitelisted! Will copy the current docs to the [${CURRENT_BRANCH}] folder" + for f in docs/target/generated-docs/*; do + file=${f#docs/target/generated-docs/*} + if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then + # Not ignored... + # We want users to access 2.0.0.BUILD-SNAPSHOT/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html + if [[ "${file}" == "${MAIN_ADOC_VALUE}.html" ]] ; then + # We don't want to copy the spring-cloud-sleuth.html + # we want it to be converted to index.html + cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html + git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html + else + cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH} + git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/$file + fi + fi + done + COMMIT_CHANGES="yes" + else + echo -e "Branch [${CURRENT_BRANCH}] is not on the white list! Check out the Maven [${WHITELIST_PROPERTY}] property in + [docs] module available under [docs] profile. Won't commit any changes to gh-pages for this branch." + fi + fi +} + +# Copies the docs by using the explicitly provided version +function copy_docs_for_provided_version() { + local FOLDER=${DESTINATION_REPO_FOLDER}/${VERSION} + mkdir -p ${FOLDER} + echo -e "Current tag is [v${VERSION}] Will copy the current docs to the [${FOLDER}] folder" + for f in ${ROOT_FOLDER}/docs/target/generated-docs/*; do + file=${f#${ROOT_FOLDER}/docs/target/generated-docs/*} + copy_docs_for_branch ${file} ${FOLDER} + done + COMMIT_CHANGES="yes" + CURRENT_BRANCH="v${VERSION}" +} + +# Copies the docs from target to the provided destination +# Params: +# $1 - file from target +# $2 - destination to which copy the files +function copy_docs_for_branch() { + local file=$1 + local destination=$2 + if ! git ls-files -i -o --exclude-standard --directory | grep -q ^${file}$; then + # Not ignored... + # We want users to access 2.0.0.BUILD-SNAPSHOT/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html + if [[ ("${file}" == "${MAIN_ADOC_VALUE}.html") || ("${file}" == "${REPO_NAME}.html") ]] ; then + # We don't want to copy the spring-cloud-sleuth.html + # we want it to be converted to index.html + cp -rf $f ${destination}/index.html + git add -A ${destination}/index.html + else + cp -rf $f ${destination} + git add -A ${destination}/$file + fi + fi +} + +function commit_changes_if_applicable() { + if [[ "${COMMIT_CHANGES}" == "yes" ]] ; then + COMMIT_SUCCESSFUL="no" + git commit -a -m "Sync docs from ${CURRENT_BRANCH} to gh-pages" && COMMIT_SUCCESSFUL="yes" || echo "Failed to commit changes" + + # Uncomment the following push if you want to auto push to + # the gh-pages branch whenever you commit to master locally. + # This is a little extreme. Use with care! + ################################################################### + if [[ "${COMMIT_SUCCESSFUL}" == "yes" ]] ; then + git push origin gh-pages + fi + fi +} + +# Switch back to the previous branch and exit block +function checkout_previous_branch() { + # If -version was provided we need to come back to root project + cd ${ROOT_FOLDER} + git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script" + if [ "$dirty" != "0" ]; then git stash pop; fi + exit 0 +} + +# Assert if properties have been properly passed +function assert_properties() { +echo "VERSION [${VERSION}], DESTINATION [${DESTINATION}], CLONE [${CLONE}]" +if [[ "${VERSION}" != "" && (-z "${DESTINATION}" && -z "${CLONE}") ]] ; then echo "Version was set but destination / clone was not!"; exit 1;fi +if [[ ("${DESTINATION}" != "" && "${CLONE}" != "") && -z "${VERSION}" ]] ; then echo "Destination / clone was set but version was not!"; exit 1;fi +if [[ "${DESTINATION}" != "" && "${CLONE}" == "yes" ]] ; then echo "Destination and clone was set. Pick one!"; exit 1;fi +} + +# Prints the usage +function print_usage() { +cat </` +- if the destination switch is passed (-d) then the script will check if the provided dir is a git repo and then will + switch to gh-pages of that repo and copy the generated docs to `docs//` + +USAGE: + +You can use the following options: + +-v|--version - the script will apply the whole procedure for a particular library version +-d|--destination - the root of destination folder where the docs should be copied. You have to use the full path. + E.g. point to spring-cloud-static folder. Can't be used with (-c) +-b|--build - will run the standard build process after checking out the branch +-c|--clone - will automatically clone the spring-cloud-static repo instead of providing the destination. + Obviously can't be used with (-d) + +EOF +} + + +# ========================================== +# ____ ____ _____ _____ _____ _______ +# / ____|/ ____| __ \|_ _| __ \__ __| +# | (___ | | | |__) | | | | |__) | | | +# \___ \| | | _ / | | | ___/ | | +# ____) | |____| | \ \ _| |_| | | | +# |_____/ \_____|_| \_\_____|_| |_| +# +# ========================================== + +while [[ $# > 0 ]] +do +key="$1" +case ${key} in + -v|--version) + VERSION="$2" + shift # past argument + ;; + -d|--destination) + DESTINATION="$2" + shift # past argument + ;; + -b|--build) + BUILD="yes" + ;; + -c|--clone) + CLONE="yes" + ;; + -h|--help) + print_usage + exit 0 + ;; + *) + echo "Invalid option: [$1]" + print_usage + exit 1 + ;; +esac +shift # past argument or value +done + +assert_properties +set_default_props +check_if_anything_to_sync +if [[ -z "${VERSION}" ]] ; then + retrieve_current_branch +else + switch_to_tag +fi +build_docs_if_applicable +retrieve_doc_properties +stash_changes +add_docs_from_target +checkout_previous_branch \ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/background.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/background.png new file mode 100644 index 00000000..15dca6fb Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/background.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/callouts/1.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/callouts/1.png new file mode 100644 index 00000000..7d473430 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/callouts/1.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/callouts/2.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/callouts/2.png new file mode 100644 index 00000000..5d09341b Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/callouts/2.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/callouts/3.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/callouts/3.png new file mode 100644 index 00000000..ef7b7004 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/callouts/3.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/caution.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/caution.png new file mode 100644 index 00000000..8a5e4fca Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/caution.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/important.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/important.png new file mode 100644 index 00000000..ec54df65 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/important.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/logo.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/logo.png new file mode 100644 index 00000000..ade2ce6e Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/logo.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/note.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/note.png new file mode 100644 index 00000000..88d997b1 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/note.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/part-bindings.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/part-bindings.png new file mode 100644 index 00000000..da8d8612 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/part-bindings.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/part-exchange.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/part-exchange.png new file mode 100644 index 00000000..54eff269 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/part-exchange.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/part-queues.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/part-queues.png new file mode 100644 index 00000000..0fe7eb70 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/part-queues.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/rabbit-binder.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/rabbit-binder.png new file mode 100644 index 00000000..aabf698b Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/rabbit-binder.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/tip.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/tip.png new file mode 100644 index 00000000..6530abb4 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/tip.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/warning.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/warning.png new file mode 100644 index 00000000..0d5b5244 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/images/warning.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/css/highlight.css b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/css/highlight.css new file mode 100644 index 00000000..3850f8b9 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/css/highlight.css @@ -0,0 +1,35 @@ +/* + code highlight CSS resemblign the Eclipse IDE default color schema + @author Costin Leau +*/ + +.hl-keyword { + color: #7F0055; + font-weight: bold; +} + +.hl-comment { + color: #3F5F5F; + font-style: italic; +} + +.hl-multiline-comment { + color: #3F5FBF; + font-style: italic; +} + +.hl-tag { + color: #3F7F7F; +} + +.hl-attribute { + color: #7F007F; +} + +.hl-value { + color: #2A00FF; +} + +.hl-string { + color: #2A00FF; +} \ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/css/manual-multipage.css b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/css/manual-multipage.css new file mode 100644 index 00000000..b790654b --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/css/manual-multipage.css @@ -0,0 +1,9 @@ +@IMPORT url("manual.css"); + +body.firstpage { + background: url("../images/background.png") no-repeat center top; +} + +div.part h1 { + border-top: none; +} diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/css/manual-singlepage.css b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/css/manual-singlepage.css new file mode 100644 index 00000000..303192a8 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/css/manual-singlepage.css @@ -0,0 +1,6 @@ +@IMPORT url("manual.css"); + +body { + background: url("../images/background.png") no-repeat center top; +} + diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/css/manual.css b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/css/manual.css new file mode 100644 index 00000000..20cf07da --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/css/manual.css @@ -0,0 +1,342 @@ +@IMPORT url("highlight.css"); + +html { + padding: 0pt; + margin: 0pt; +} + +body { + color: #333333; + margin: 15px 30px; + font-family: Helvetica, Arial, Freesans, Clean, Sans-serif; + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} + +code { + font-size: 16px; + font-family: Consolas, "Liberation Mono", Courier, monospace; +} + +:not(a) > code { + color: #6D180B; +} + +:not(pre) > code { + background-color: #F2F2F2; + border: 1px solid #CCCCCC; + border-radius: 4px; + padding: 1px 3px 0; + text-shadow: none; + white-space: nowrap; +} + +body > *:first-child { + margin-top: 0 !important; +} + +div { + margin: 0pt; +} + +hr { + border: 1px solid #CCCCCC; + background: #CCCCCC; +} + +h1, h2, h3, h4, h5, h6 { + color: #000000; + cursor: text; + font-weight: bold; + margin: 30px 0 10px; + padding: 0; +} + +h1, h2, h3 { + margin: 40px 0 10px; +} + +h1 { + margin: 70px 0 30px; + padding-top: 20px; +} + +div.part h1 { + border-top: 1px dotted #CCCCCC; +} + +h1, h1 code { + font-size: 32px; +} + +h2, h2 code { + font-size: 24px; +} + +h3, h3 code { + font-size: 20px; +} + +h4, h1 code, h5, h5 code, h6, h6 code { + font-size: 18px; +} + +div.book, div.chapter, div.appendix, div.part, div.preface { + min-width: 300px; + max-width: 1200px; + margin: 0 auto; +} + +p.releaseinfo { + font-weight: bold; + margin-bottom: 40px; + margin-top: 40px; +} + +div.authorgroup { + line-height: 1; +} + +p.copyright { + line-height: 1; + margin-bottom: -5px; +} + +.legalnotice p { + font-style: italic; + font-size: 14px; + line-height: 1; +} + +div.titlepage + p, div.titlepage + p { + margin-top: 0; +} + +pre { + line-height: 1.0; + color: black; +} + +a { + color: #4183C4; + text-decoration: none; +} + +p { + margin: 15px 0; + text-align: left; +} + +ul, ol { + padding-left: 30px; +} + +li p { + margin: 0; +} + +div.table { + margin: 1em; + padding: 0.5em; + text-align: center; +} + +div.table table, div.informaltable table { + display: table; + width: 100%; +} + +div.table td { + padding-left: 7px; + padding-right: 7px; +} + +.sidebar { + line-height: 1.4; + padding: 0 20px; + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; +} + +.sidebar p.title { + color: #6D180B; +} + +pre.programlisting, pre.screen { + font-size: 15px; + padding: 6px 10px; + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; + clear: both; + overflow: auto; + line-height: 1.4; + font-family: Consolas, "Liberation Mono", Courier, monospace; +} + +table { + border-collapse: collapse; + border-spacing: 0; + border: 1px solid #DDDDDD !important; + border-radius: 4px !important; + border-collapse: separate !important; + line-height: 1.6; +} + +table thead { + background: #F5F5F5; +} + +table tr { + border: none; + border-bottom: none; +} + +table th { + font-weight: bold; +} + +table th, table td { + border: none !important; + padding: 6px 13px; +} + +table tr:nth-child(2n) { + background-color: #F8F8F8; +} + +td p { + margin: 0 0 15px 0; +} + +div.table-contents td p { + margin: 0; +} + +div.important *, div.note *, div.tip *, div.warning *, div.navheader *, div.navfooter *, div.calloutlist * { + border: none !important; + background: none !important; + margin: 0; +} + +div.important p, div.note p, div.tip p, div.warning p { + color: #6F6F6F; + line-height: 1.6; +} + +div.important code, div.note code, div.tip code, div.warning code { + background-color: #F2F2F2 !important; + border: 1px solid #CCCCCC !important; + border-radius: 4px !important; + padding: 1px 3px 0 !important; + text-shadow: none !important; + white-space: nowrap !important; +} + +.note th, .tip th, .warning th { + display: none; +} + +.note tr:first-child td, .tip tr:first-child td, .warning tr:first-child td { + border-right: 1px solid #CCCCCC !important; + padding-top: 10px; +} + +div.calloutlist p, div.calloutlist td { + padding: 0; + margin: 0; +} + +div.calloutlist > table > tbody > tr > td:first-child { + padding-left: 10px; + width: 30px !important; +} + +div.important, div.note, div.tip, div.warning { + margin-left: 0px !important; + margin-right: 20px !important; + margin-top: 20px; + margin-bottom: 20px; + padding-top: 10px; + padding-bottom: 10px; +} + +div.toc { + line-height: 1.2; +} + +dl, dt { + margin-top: 1px; + margin-bottom: 0; +} + +div.toc > dl > dt { + font-size: 32px; + font-weight: bold; + margin: 30px 0 10px 0; + display: block; +} + +div.toc > dl > dd > dl > dt { + font-size: 24px; + font-weight: bold; + margin: 20px 0 10px 0; + display: block; +} + +div.toc > dl > dd > dl > dd > dl > dt { + font-weight: bold; + font-size: 20px; + margin: 10px 0 0 0; +} + +tbody.footnotes * { + border: none !important; +} + +div.footnote p { + margin: 0; + line-height: 1; +} + +div.footnote p sup { + margin-right: 6px; + vertical-align: middle; +} + +div.navheader { + border-bottom: 1px solid #CCCCCC; +} + +div.navfooter { + border-top: 1px solid #CCCCCC; +} + +.title { + margin-left: -1em; + padding-left: 1em; +} + +.title > a { + position: absolute; + visibility: hidden; + display: block; + font-size: 0.85em; + margin-top: 0.05em; + margin-left: -1em; + vertical-align: text-top; + color: black; +} + +.title > a:before { + content: "\00A7"; +} + +.title:hover > a, .title > a:hover, .title:hover > a:hover { + visibility: visible; +} + +.title:focus > a, .title > a:focus, .title:focus > a:focus { + outline: 0; +} diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/background.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/background.png new file mode 100644 index 00000000..15dca6fb Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/background.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/callouts/1.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/callouts/1.png new file mode 100644 index 00000000..7d473430 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/callouts/1.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/callouts/2.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/callouts/2.png new file mode 100644 index 00000000..5d09341b Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/callouts/2.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/callouts/3.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/callouts/3.png new file mode 100644 index 00000000..ef7b7004 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/callouts/3.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/caution.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/caution.png new file mode 100644 index 00000000..8a5e4fca Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/caution.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/important.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/important.png new file mode 100644 index 00000000..ec54df65 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/important.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/logo.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/logo.png new file mode 100644 index 00000000..ade2ce6e Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/logo.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/note.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/note.png new file mode 100644 index 00000000..88d997b1 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/note.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/tip.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/tip.png new file mode 100644 index 00000000..6530abb4 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/tip.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/warning.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/warning.png new file mode 100644 index 00000000..0d5b5244 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/images/warning.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__appendices.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__appendices.html new file mode 100644 index 00000000..4971781a --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__appendices.html @@ -0,0 +1,3 @@ + + + Part II. Appendices

Part II. Appendices

\ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__configuration_options.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__configuration_options.html new file mode 100644 index 00000000..b8401f14 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__configuration_options.html @@ -0,0 +1,98 @@ + + + 3. Configuration Options

3. Configuration Options

This section contains settings specific to the RabbitMQ Binder and bound channels.

For general binding configuration options and properties, see the Spring Cloud Stream core documentation.

3.1 RabbitMQ Binder Properties

By default, the RabbitMQ binder uses Spring Boot’s ConnectionFactory. +Conseuqently, it supports all Spring Boot configuration options for RabbitMQ. +(For reference, see the Spring Boot documentation). +RabbitMQ configuration options use the spring.rabbitmq prefix.

In addition to Spring Boot options, the RabbitMQ binder supports the following properties:

spring.cloud.stream.rabbit.binder.adminAddresses

A comma-separated list of RabbitMQ management plugin URLs. +Only used when nodes contains more than one entry. +Each entry in this list must have a corresponding entry in spring.rabbitmq.addresses. +Only needed if you use a RabbitMQ cluster and wish to consume from the node that hosts the queue. +See Queue Affinity and the LocalizedQueueConnectionFactory for more information.

Default: empty.

spring.cloud.stream.rabbit.binder.nodes

A comma-separated list of RabbitMQ node names. +When more than one entry, used to locate the server address where a queue is located. +Each entry in this list must have a corresponding entry in spring.rabbitmq.addresses. +Only needed if you use a RabbitMQ cluster and wish to consume from the node that hosts the queue. +See Queue Affinity and the LocalizedQueueConnectionFactory for more information.

Default: empty.

spring.cloud.stream.rabbit.binder.compressionLevel

The compression level for compressed bindings. +See java.util.zip.Deflater.

Default: 1 (BEST_LEVEL).

spring.cloud.stream.binder.connection-name-prefix

A connection name prefix used to name the connection(s) created by this binder. +The name is this prefix followed by #n, where n increments each time a new connection is opened.

Default: none (Spring AMQP default).

3.2 RabbitMQ Consumer Properties

The following properties are available for Rabbit consumers only and must be prefixed with spring.cloud.stream.rabbit.bindings.<channelName>.consumer..

acknowledgeMode

The acknowledge mode.

Default: AUTO.

anonymousGroupPrefix

When the binding has no group property, an anonymous, auto-delete queue is bound to the destination exchange. +The default naming stragegy for such queues results in a queue named anonymous.<base64 representation of a UUID>. +Set this property to change the prefix to something other than the default.

Default: anonymous..

autoBindDlq

Whether to automatically declare the DLQ and bind it to the binder DLX.

Default: false.

bindingRoutingKey

The routing key with which to bind the queue to the exchange (if bindQueue is true). +For partitioned destinations, -<instanceIndex> is appended.

Default: #.

bindQueue

Whether to declare the queue and bind it to the destination exchange. +Set it to false if you have set up your own infrastructure and have previously created and bound the queue.

Default: true.

consumerTagPrefix

Used to create the consumer tag(s); will be appended by #n where n increments for each consumer created. +Example: ${spring.application.name}-${spring.cloud.stream.bindings.input.group}-${spring.cloud.stream.instance-index}.

Default: none - the broker will generate random consumer tags.

containerType

Select the type of listener container to be used. +See Choosing a Container in the Spring AMQP documentation for more information.

Default: simple

deadLetterQueueName

The name of the DLQ

Default: prefix+destination.dlq

deadLetterExchange

A DLX to assign to the queue. +Relevant only if autoBindDlq is true.

Default: 'prefix+DLX'

deadLetterExchangeType

The type of the DLX to assign to the queue. +Relevant only if autoBindDlq is true.

Default: 'direct'

deadLetterRoutingKey

A dead letter routing key to assign to the queue. +Relevant only if autoBindDlq is true.

Default: destination

declareDlx

Whether to declare the dead letter exchange for the destination. +Relevant only if autoBindDlq is true. +Set to false if you have a pre-configured DLX.

Default: true.

declareExchange

Whether to declare the exchange for the destination.

Default: true.

delayedExchange

Whether to declare the exchange as a Delayed Message Exchange. +Requires the delayed message exchange plugin on the broker. +The x-delayed-type argument is set to the exchangeType.

Default: false.

dlqDeadLetterExchange

If a DLQ is declared, a DLX to assign to that queue.

Default: none

dlqDeadLetterRoutingKey

If a DLQ is declared, a dead letter routing key to assign to that queue.

Default: none

dlqExpires

How long before an unused dead letter queue is deleted (in milliseconds).

Default: no expiration

dlqLazy

Declare the dead letter queue with the x-queue-mode=lazy argument. +See Lazy Queues. +Consider using a policy instead of this setting, because using a policy allows changing the setting without deleting the queue.

Default: false.

dlqMaxLength

Maximum number of messages in the dead letter queue.

Default: no limit

dlqMaxLengthBytes

Maximum number of total bytes in the dead letter queue from all messages.

Default: no limit

dlqMaxPriority

Maximum priority of messages in the dead letter queue (0-255).

Default: none

dlqOverflowBehavior

Action to take when dlqMaxLength or dlqMaxLengthBytes is exceeded; currently drop-head or reject-publish but refer to the RabbitMQ documentation.

Default: none

dlqTtl

Default time to live to apply to the dead letter queue when declared (in milliseconds).

Default: no limit

durableSubscription

Whether the subscription should be durable. +Only effective if group is also set.

Default: true.

exchangeAutoDelete

If declareExchange is true, whether the exchange should be auto-deleted (that is, removed after the last queue is removed).

Default: true.

exchangeDurable

If declareExchange is true, whether the exchange should be durable (that is, it survives broker restart).

Default: true.

exchangeType

The exchange type: direct, fanout or topic for non-partitioned destinations and direct or topic for partitioned destinations.

Default: topic.

exclusive

Whether to create an exclusive consumer. +Concurrency should be 1 when this is true. +Often used when strict ordering is required but enabling a hot standby instance to take over after a failure. +See recoveryInterval, which controls how often a standby instance attempts to consume.

Default: false.

expires

How long before an unused queue is deleted (in milliseconds).

Default: no expiration

failedDeclarationRetryInterval

The interval (in milliseconds) between attempts to consume from a queue if it is missing.

Default: 5000

frameMaxHeadroom

The number of bytes to reserve for other headers when adding the stack trace to a DLQ message header. +All headers must fit within the frame_max size configured on the broker. +Stack traces can be large; if the size plus this property exceeds frame_max then the stack trace will be truncated. +A WARN log will be written; consider increasing the frame_max or reducing the stack trace by catching the exception and throwing one with a smaller stack trace.

Default: 20000

headerPatterns

Patterns for headers to be mapped from inbound messages.

Default: ['*'] (all headers).

lazy

Declare the queue with the x-queue-mode=lazy argument. +See Lazy Queues. +Consider using a policy instead of this setting, because using a policy allows changing the setting without deleting the queue.

Default: false.

maxConcurrency

The maximum number of consumers. +Not supported when the containerType is direct.

Default: 1.

maxLength

The maximum number of messages in the queue.

Default: no limit

maxLengthBytes

The maximum number of total bytes in the queue from all messages.

Default: no limit

maxPriority

The maximum priority of messages in the queue (0-255).

Default: none

missingQueuesFatal

When the queue cannot be found, whether to treat the condition as fatal and stop the listener container. +Defaults to false so that the container keeps trying to consume from the queue — for example, when using a cluster and the node hosting a non-HA queue is down.

Default: false

overflowBehavior

Action to take when maxLength or maxLengthBytes is exceeded; currently drop-head or reject-publish but refer to the RabbitMQ documentation.

Default: none

prefetch

Prefetch count.

Default: 1.

prefix

A prefix to be added to the name of the destination and queues.

Default: "".

queueDeclarationRetries

The number of times to retry consuming from a queue if it is missing. +Relevant only when missingQueuesFatal is true. +Otherwise, the container keeps retrying indefinitely. +Not supported when the containerType is direct.

Default: 3

queueNameGroupOnly

When true, consume from a queue with a name equal to the group. +Otherwise the queue name is destination.group. +This is useful, for example, when using Spring Cloud Stream to consume from an existing RabbitMQ queue.

Default: false.

recoveryInterval

The interval between connection recovery attempts, in milliseconds.

Default: 5000.

requeueRejected

Whether delivery failures should be re-queued when retry is disabled or republishToDlq is false.

Default: false.

republishDeliveryMode

When republishToDlq is true, specifies the delivery mode of the republished message.

Default: DeliveryMode.PERSISTENT

republishToDlq

By default, messages that fail after retries are exhausted are rejected. +If a dead-letter queue (DLQ) is configured, RabbitMQ routes the failed message (unchanged) to the DLQ. +If set to true, the binder republishs failed messages to the DLQ with additional headers, including the exception message and stack trace from the cause of the final failure. +Also see the frameMaxHeadroom property.

Default: false

transacted

Whether to use transacted channels.

Default: false.

ttl

Default time to live to apply to the queue when declared (in milliseconds).

Default: no limit

txSize

The number of deliveries between acks. +Not supported when the containerType is direct.

Default: 1.

3.3 Advanced Listener Container Configuration

To set listener container properties that are not exposed as binder or binding properties, add a single bean of type ListenerContainerCustomizer to the application context. +The binder and binding properties will be set and then the customizer will be called. +The customizer (configure() method) is provided with the queue name as well as the consumer group as arguments.

3.4 Rabbit Producer Properties

The following properties are available for Rabbit producers only and +must be prefixed with spring.cloud.stream.rabbit.bindings.<channelName>.producer..

autoBindDlq

Whether to automatically declare the DLQ and bind it to the binder DLX.

Default: false.

batchingEnabled

Whether to enable message batching by producers. +Messages are batched into one message according to the following properties (described in the next three entries in this list): 'batchSize', batchBufferLimit, and batchTimeout. +See Batching for more information.

Default: false.

batchSize

The number of messages to buffer when batching is enabled.

Default: 100.

batchBufferLimit

The maximum buffer size when batching is enabled.

Default: 10000.

batchTimeout

The batch timeout when batching is enabled.

Default: 5000.

bindingRoutingKey

The routing key with which to bind the queue to the exchange (if bindQueue is true). +Only applies to non-partitioned destinations. +Only applies if requiredGroups are provided and then only to those groups.

Default: #.

bindQueue

Whether to declare the queue and bind it to the destination exchange. +Set it to false if you have set up your own infrastructure and have previously created and bound the queue. +Only applies if requiredGroups are provided and then only to those groups.

Default: true.

compress

Whether data should be compressed when sent.

Default: false.

confirmAckChannel

When errorChannelEnabled is true, a channel to which to send positive delivery acknowledgments (aka publisher confirms). +If the channel does not exist, a DirectChannel is registered with this name. +The connection factory must be configured to enable publisher confirms.

Default: nullChannel (acks are discarded).

deadLetterQueueName

The name of the DLQ +Only applies if requiredGroups are provided and then only to those groups.

Default: prefix+destination.dlq

deadLetterExchange

A DLX to assign to the queue. +Relevant only when autoBindDlq is true. +Applies only when requiredGroups are provided and then only to those groups.

Default: 'prefix+DLX'

deadLetterExchangeType

The type of the DLX to assign to the queue. +Relevant only if autoBindDlq is true. +Applies only when requiredGroups are provided and then only to those groups.

Default: 'direct'

deadLetterRoutingKey

A dead letter routing key to assign to the queue. +Relevant only when autoBindDlq is true. +Applies only when requiredGroups are provided and then only to those groups.

Default: destination

declareDlx

Whether to declare the dead letter exchange for the destination. +Relevant only if autoBindDlq is true. +Set to false if you have a pre-configured DLX. +Applies only when requiredGroups are provided and then only to those groups.

Default: true.

declareExchange

Whether to declare the exchange for the destination.

Default: true.

delayExpression

A SpEL expression to evaluate the delay to apply to the message (x-delay header). +It has no effect if the exchange is not a delayed message exchange.

Default: No x-delay header is set.

delayedExchange

Whether to declare the exchange as a Delayed Message Exchange. +Requires the delayed message exchange plugin on the broker. +The x-delayed-type argument is set to the exchangeType.

Default: false.

deliveryMode

The delivery mode.

Default: PERSISTENT.

dlqDeadLetterExchange

When a DLQ is declared, a DLX to assign to that queue. +Applies only if requiredGroups are provided and then only to those groups.

Default: none

dlqDeadLetterRoutingKey

When a DLQ is declared, a dead letter routing key to assign to that queue. +Applies only when requiredGroups are provided and then only to those groups.

Default: none

dlqExpires

How long (in milliseconds) before an unused dead letter queue is deleted. +Applies only when requiredGroups are provided and then only to those groups.

Default: no expiration

dlqLazy
Declare the dead letter queue with the x-queue-mode=lazy argument. +See Lazy Queues. +Consider using a policy instead of this setting, because using a policy allows changing the setting without deleting the queue. +Applies only when requiredGroups are provided and then only to those groups.
dlqMaxLength

Maximum number of messages in the dead letter queue. +Applies only if requiredGroups are provided and then only to those groups.

Default: no limit

dlqMaxLengthBytes

Maximum number of total bytes in the dead letter queue from all messages. +Applies only when requiredGroups are provided and then only to those groups.

Default: no limit

dlqMaxPriority

Maximum priority of messages in the dead letter queue (0-255) +Applies only when requiredGroups are provided and then only to those groups.

Default: none

dlqTtl

Default time (in milliseconds) to live to apply to the dead letter queue when declared. +Applies only when requiredGroups are provided and then only to those groups.

Default: no limit

exchangeAutoDelete

If declareExchange is true, whether the exchange should be auto-delete (it is removed after the last queue is removed).

Default: true.

exchangeDurable

If declareExchange is true, whether the exchange should be durable (survives broker restart).

Default: true.

exchangeType

The exchange type: direct, fanout or topic for non-partitioned destinations and direct or topic for partitioned destinations.

Default: topic.

expires

How long (in milliseconds) before an unused queue is deleted. +Applies only when requiredGroups are provided and then only to those groups.

Default: no expiration

headerPatterns

Patterns for headers to be mapped to outbound messages.

Default: ['*'] (all headers).

lazy

Declare the queue with the x-queue-mode=lazy argument. +See Lazy Queues. +Consider using a policy instead of this setting, because using a policy allows changing the setting without deleting the queue. +Applies only when requiredGroups are provided and then only to those groups.

Default: false.

maxLength

Maximum number of messages in the queue. +Applies only when requiredGroups are provided and then only to those groups.

Default: no limit

maxLengthBytes

Maximum number of total bytes in the queue from all messages. +Only applies if requiredGroups are provided and then only to those groups.

Default: no limit

maxPriority

Maximum priority of messages in the queue (0-255). +Only applies if requiredGroups are provided and then only to those groups.

Default: none

prefix

A prefix to be added to the name of the destination exchange.

Default: "".

queueNameGroupOnly

When true, consume from a queue with a name equal to the group. +Otherwise the queue name is destination.group. +This is useful, for example, when using Spring Cloud Stream to consume from an existing RabbitMQ queue. +Applies only when requiredGroups are provided and then only to those groups.

Default: false.

routingKeyExpression

A SpEL expression to determine the routing key to use when publishing messages. +For a fixed routing key, use a literal expression, such as routingKeyExpression='my.routingKey' in a properties file or routingKeyExpression: '''my.routingKey''' in a YAML file.

Default: destination or destination-<partition> for partitioned destinations.

transacted

Whether to use transacted channels.

Default: false.

ttl

Default time (in milliseconds) to live to apply to the queue when declared. +Applies only when requiredGroups are provided and then only to those groups.

Default: no limit

[Note]Note

In the case of RabbitMQ, content type headers can be set by external applications. +Spring Cloud Stream supports them as part of an extended internal protocol used for any type of transport — including transports, such as Kafka (prior to 0.11), that do not natively support headers.

\ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__partitioning_with_the_rabbitmq_binder.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__partitioning_with_the_rabbitmq_binder.html new file mode 100644 index 00000000..135c5000 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__partitioning_with_the_rabbitmq_binder.html @@ -0,0 +1,80 @@ + + + 7. Partitioning with the RabbitMQ Binder

7. Partitioning with the RabbitMQ Binder

RabbitMQ does not support partitioning natively.

Sometimes, it is advantageous to send data to specific partitions — for example, when you want to strictly order message processing, all messages for a particular customer should go to the same partition.

The RabbitMessageChannelBinder provides partitioning by binding a queue for each partition to the destination exchange.

The following Java and YAML examples show how to configure the producer:

Producer.  +

@SpringBootApplication
+@EnableBinding(Source.class)
+public class RabbitPartitionProducerApplication {
+
+    private static final Random RANDOM = new Random(System.currentTimeMillis());
+
+    private static final String[] data = new String[] {
+            "abc1", "def1", "qux1",
+            "abc2", "def2", "qux2",
+            "abc3", "def3", "qux3",
+            "abc4", "def4", "qux4",
+            };
+
+    public static void main(String[] args) {
+        new SpringApplicationBuilder(RabbitPartitionProducerApplication.class)
+            .web(false)
+            .run(args);
+    }
+
+    @InboundChannelAdapter(channel = Source.OUTPUT, poller = @Poller(fixedRate = "5000"))
+    public Message<?> generate() {
+        String value = data[RANDOM.nextInt(data.length)];
+        System.out.println("Sending: " + value);
+        return MessageBuilder.withPayload(value)
+                .setHeader("partitionKey", value)
+                .build();
+    }
+
+}

+

application.yml.  +

    spring:
+      cloud:
+        stream:
+          bindings:
+            output:
+              destination: partitioned.destination
+              producer:
+                partitioned: true
+                partition-key-expression: headers['partitionKey']
+                partition-count: 2
+                required-groups:
+                - myGroup

+

[Note]Note

The configuration in the prececing example uses the default partitioning (key.hashCode() % partitionCount). +This may or may not provide a suitably balanced algorithm, depending on the key values. +You can override this default by using the partitionSelectorExpression or partitionSelectorClass properties.

The required-groups property is required only if you need the consumer queues to be provisioned when the producer is deployed. +Otherwise, any messages sent to a partition are lost until the corresponding consumer is deployed.

The following configuration provisions a topic exchange:

part exchange

The following queues are bound to that exchange:

part queues

The following bindings associate the queues to the exchange:

part bindings

The following Java and YAML examples continue the previous examples and show how to configure the consumer:

Consumer.  +

@SpringBootApplication
+@EnableBinding(Sink.class)
+public class RabbitPartitionConsumerApplication {
+
+    public static void main(String[] args) {
+        new SpringApplicationBuilder(RabbitPartitionConsumerApplication.class)
+            .web(false)
+            .run(args);
+    }
+
+    @StreamListener(Sink.INPUT)
+    public void listen(@Payload String in, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue) {
+        System.out.println(in + " received from queue " + queue);
+    }
+
+}

+

application.yml.  +

    spring:
+      cloud:
+        stream:
+          bindings:
+            input:
+              destination: partitioned.destination
+              group: myGroup
+              consumer:
+                partitioned: true
+                instance-index: 0

+

[Important]Important

The RabbitMessageChannelBinder does not support dynamic scaling. +There must be at least one consumer per partition. +The consumer’s instanceIndex is used to indicate which partition is consumed. +Platforms such as Cloud Foundry can have only one instance with an instanceIndex.

\ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__rabbitmq_binder_overview.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__rabbitmq_binder_overview.html new file mode 100644 index 00000000..a41c577d --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__rabbitmq_binder_overview.html @@ -0,0 +1,21 @@ + + + 2. RabbitMQ Binder Overview

2. RabbitMQ Binder Overview

The following simplified diagram shows how the RabbitMQ binder operates:

Figure 2.1. RabbitMQ Binder

rabbit binder

By default, the RabbitMQ Binder implementation maps each destination to a TopicExchange. +For each consumer group, a Queue is bound to that TopicExchange. +Each consumer instance has a corresponding RabbitMQ Consumer instance for its group’s Queue. +For partitioned producers and consumers, the queues are suffixed with the partition index and use the partition index as the routing key. +For anonymous consumers (those with no group property), an auto-delete queue (with a randomized unique name) is used.

By using the optional autoBindDlq option, you can configure the binder to create and configure dead-letter queues (DLQs) (and a dead-letter exchange DLX, as well as routing infrastructure). +By default, the dead letter queue has the name of the destination, appended with .dlq. +If retry is enabled (maxAttempts > 1), failed messages are delivered to the DLQ after retries are exhausted. +If retry is disabled (maxAttempts = 1), you should set requeueRejected to false (the default) so that failed messages are routed to the DLQ, instead of being re-queued. +In addition, republishToDlq causes the binder to publish a failed message to the DLQ (instead of rejecting it). +This feature lets additional information (such as the stack trace in the x-exception-stacktrace header) be added to the message in headers. +See the frameMaxHeadroom property for information about truncated stack traces. +This option does not need retry enabled. +You can republish a failed message after just one attempt. +Starting with version 1.2, you can configure the delivery mode of republished messages. +See the republishDeliveryMode property.

If the stream listener throws an ImmediateAcknowledgeAmqpException, the DLQ is bypassed and the message simply discarded. +Starting with version 2.1, this is true regardless of the setting of republishToDlq; previously it was only the case when republishToDlq was false.

[Important]Important

Setting requeueRejected to true (with republishToDlq=false ) causes the message to be re-queued and redelivered continually, which is likely not what you want unless the reason for the failure is transient. +In general, you should enable retry within the binder by setting maxAttempts to greater than one or by setting republishToDlq to true.

See Section 3.1, “RabbitMQ Binder Properties” for more information about these properties.

The framework does not provide any standard mechanism to consume dead-letter messages (or to re-route them back to the primary queue). +Some options are described in Chapter 6, Dead-Letter Queue Processing.

[Note]Note

When multiple RabbitMQ binders are used in a Spring Cloud Stream application, it is important to disable 'RabbitAutoConfiguration' to avoid the same configuration from RabbitAutoConfiguration being applied to the two binders. +You can exclude the class by using the @SpringBootApplication annotation.

Starting with version 2.0, the RabbitMessageChannelBinder sets the RabbitTemplate.userPublisherConnection property to true so that the non-transactional producers avoid deadlocks on consumers, which can happen if cached connections are blocked because of a memory alarm on the broker.

[Note]Note

Currently, a multiplex consumer (a single consumer listening to multiple queues) is only supported for message-driven conssumers; polled consumers can only retrieve messages from a single queue.

\ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__reference_guide.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__reference_guide.html new file mode 100644 index 00000000..dc904356 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__reference_guide.html @@ -0,0 +1,4 @@ + + + Part I. Reference Guide

Part I. Reference Guide

This guide describes the RabbitMQ implementation of the Spring Cloud Stream Binder. +It contains information about its design, usage and configuration options, as well as information on how the Stream Cloud Stream concepts map into RabbitMQ specific constructs.

\ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__retry_with_the_rabbitmq_binder.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__retry_with_the_rabbitmq_binder.html new file mode 100644 index 00000000..2f31deda --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__retry_with_the_rabbitmq_binder.html @@ -0,0 +1,42 @@ + + + 4. Retry With the RabbitMQ Binder

4. Retry With the RabbitMQ Binder

When retry is enabled within the binder, the listener container thread is suspended for any back off periods that are configured. +This might be important when strict ordering is required with a single consumer. However, for other use cases, it prevents other messages from being processed on that thread. +An alternative to using binder retry is to set up dead lettering with time to live on the dead-letter queue (DLQ) as well as dead-letter configuration on the DLQ itself. +See Section 3.1, “RabbitMQ Binder Properties” for more information about the properties discussed here. +You can use the following example configuration to enable this feature:

  • Set autoBindDlq to true. +The binder create a DLQ. +Optionally, you can specify a name in deadLetterQueueName.
  • Set dlqTtl to the back off time you want to wait between redeliveries.
  • Set the dlqDeadLetterExchange to the default exchange. +Expired messages from the DLQ are routed to the original queue, because the default deadLetterRoutingKey is the queue name (destination.group). +Setting to the default exchange is achieved by setting the property with no value, as shown in the next example.

To force a message to be dead-lettered, either throw an AmqpRejectAndDontRequeueException or set requeueRejected to true (the default) and throw any exception.

The loop continue without end, which is fine for transient problems, but you may want to give up after some number of attempts. +Fortunately, RabbitMQ provides the x-death header, which lets you determine how many cycles have occurred.

To acknowledge a message after giving up, throw an ImmediateAcknowledgeAmqpException.

4.1 Putting it All Together

The following configuration creates an exchange myDestination with queue myDestination.consumerGroup bound to a topic exchange with a wildcard routing key #:

---
+spring.cloud.stream.bindings.input.destination=myDestination
+spring.cloud.stream.bindings.input.group=consumerGroup
+#disable binder retries
+spring.cloud.stream.bindings.input.consumer.max-attempts=1
+#dlx/dlq setup
+spring.cloud.stream.rabbit.bindings.input.consumer.auto-bind-dlq=true
+spring.cloud.stream.rabbit.bindings.input.consumer.dlq-ttl=5000
+spring.cloud.stream.rabbit.bindings.input.consumer.dlq-dead-letter-exchange=
+---

This configuration creates a DLQ bound to a direct exchange (DLX) with a routing key of myDestination.consumerGroup. +When messages are rejected, they are routed to the DLQ. +After 5 seconds, the message expires and is routed to the original queue by using the queue name as the routing key, as shown in the following example:

Spring Boot application.  +

@SpringBootApplication
+@EnableBinding(Sink.class)
+public class XDeathApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(XDeathApplication.class, args);
+    }
+
+    @StreamListener(Sink.INPUT)
+    public void listen(String in, @Header(name = "x-death", required = false) Map<?,?> death) {
+        if (death != null && death.get("count").equals(3L)) {
+            // giving up - don't send to DLX
+            throw new ImmediateAcknowledgeAmqpException("Failed after 4 attempts");
+        }
+        throw new AmqpRejectAndDontRequeueException("failed");
+    }
+
+}

+

Notice that the count property in the x-death header is a Long.

\ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__usage.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__usage.html new file mode 100644 index 00000000..02c16615 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi__usage.html @@ -0,0 +1,9 @@ + + + 1. Usage

1. Usage

To use the RabbitMQ binder, you can add it to your Spring Cloud Stream application, by using the following Maven coordinates:

<dependency>
+  <groupId>org.springframework.cloud</groupId>
+  <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
+</dependency>

Alternatively, you can use the Spring Cloud Stream RabbitMQ Starter, as follows:

<dependency>
+  <groupId>org.springframework.cloud</groupId>
+  <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
+</dependency>
\ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_building.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_building.html new file mode 100644 index 00000000..1774df69 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_building.html @@ -0,0 +1,33 @@ + + + Appendix A. Building

Appendix A. Building

A.1 Basic Compile and Test

To build the source you will need to install JDK 1.8.

The build uses the Maven wrapper so you don’t have to install a specific +version of Maven. To enable the tests, you should have RabbitMQ server running +on localhost and the default port (5672) +before building.

The main build command is

$ ./mvnw clean install

You can also add '-DskipTests' if you like, to avoid running the tests.

[Note]Note

You can also install Maven (>=3.3.3) yourself and run the mvn command +in place of ./mvnw in the examples below. If you do that you also +might need to add -P spring if your local Maven settings do not +contain repository declarations for spring pre-release artifacts.

[Note]Note

Be aware that you might need to increase the amount of memory +available to Maven by setting a MAVEN_OPTS environment variable with +a value like -Xmx512m -XX:MaxPermSize=128m. We try to cover this in +the .mvn configuration, so if you find you have to do it to make a +build succeed, please raise a ticket to get the settings added to +source control.

The projects that require middleware generally include a +docker-compose.yml, so consider using +Docker Compose to run the middeware servers +in Docker containers.

A.2 Documentation

There is a "full" profile that will generate documentation.

A.3 Working with the code

If you don’t have an IDE preference we would recommend that you use +Spring Tools Suite or +Eclipse when working with the code. We use the +m2eclipe eclipse plugin for maven support. Other IDEs and tools +should also work without issue.

A.3.1 Importing into eclipse with m2eclipse

We recommend the m2eclipe eclipse plugin when working with +eclipse. If you don’t already have m2eclipse installed it is available from the "eclipse +marketplace".

Unfortunately m2e does not yet support Maven 3.3, so once the projects +are imported into Eclipse you will also need to tell m2eclipse to use +the .settings.xml file for the projects. If you do not do this you +may see many different errors related to the POMs in the +projects. Open your Eclipse preferences, expand the Maven +preferences, and select User Settings. In the User Settings field +click Browse and navigate to the Spring Cloud project you imported +selecting the .settings.xml file in that project. Click Apply and +then OK to save the preference changes.

[Note]Note

Alternatively you can copy the repository settings from .settings.xml into your own ~/.m2/settings.xml.

A.3.2 Importing into eclipse without m2eclipse

If you prefer not to use m2eclipse you can generate eclipse project metadata using the +following command:

$ ./mvnw eclipse:eclipse

The generated eclipse projects can be imported by selecting import existing projects +from the file menu.

\ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_contributing.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_contributing.html new file mode 100644 index 00000000..fa7f1d52 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_contributing.html @@ -0,0 +1,26 @@ + + + Appendix B. Contributing

Appendix B. Contributing

Spring Cloud is released under the non-restrictive Apache 2.0 license, +and follows a very standard Github development process, using Github +tracker for issues and merging pull requests into master. If you want +to contribute even something trivial please do not hesitate, but +follow the guidelines below.

B.1 Sign the Contributor License Agreement

Before we accept a non-trivial patch or pull request we will need you to sign the +contributor’s agreement. +Signing the contributor’s agreement does not grant anyone commit rights to the main +repository, but it does mean that we can accept your contributions, and you will get an +author credit if we do. Active contributors might be asked to join the core team, and +given the ability to merge pull requests.

B.2 Code Conventions and Housekeeping

None of these is essential for a pull request, but they will all help. They can also be +added after the original pull request but before a merge.

  • Use the Spring Framework code format conventions. If you use Eclipse +you can import formatter settings using the +eclipse-code-formatter.xml file from the +Spring +Cloud Build project. If using IntelliJ, you can use the +Eclipse Code Formatter +Plugin to import the same file.
  • Make sure all new .java files to have a simple Javadoc class comment with at least an +@author tag identifying you, and preferably at least a paragraph on what the class is +for.
  • Add the ASF license header comment to all new .java files (copy from existing files +in the project)
  • Add yourself as an @author to the .java files that you modify substantially (more +than cosmetic changes).
  • Add some Javadocs and, if you change the namespace, some XSD doc elements.
  • A few unit tests would help a lot as well — someone has to do it.
  • If no-one else is using your branch, please rebase it against the current master (or +other target branch in the main project).
  • When writing a commit message please follow these conventions, +if you are fixing an existing issue please add Fixes gh-XXXX at the end of the commit +message (where XXXX is the issue number).
\ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_rabbit-dlq-processing.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_rabbit-dlq-processing.html new file mode 100644 index 00000000..4fe2b652 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_rabbit-dlq-processing.html @@ -0,0 +1,204 @@ + + + 6. Dead-Letter Queue Processing

6. Dead-Letter Queue Processing

Because you cannot anticipate how users would want to dispose of dead-lettered messages, the framework does not provide any standard mechanism to handle them. +If the reason for the dead-lettering is transient, you may wish to route the messages back to the original queue. +However, if the problem is a permanent issue, that could cause an infinite loop. +The following Spring Boot application shows an example of how to route those messages back to the original queue but moves them to a third parking lot queue after three attempts. +The second example uses the RabbitMQ Delayed Message Exchange to introduce a delay to the re-queued message. +In this example, the delay increases for each attempt. +These examples use a @RabbitListener to receive messages from the DLQ. +You could also use RabbitTemplate.receive() in a batch process.

The examples assume the original destination is so8400in and the consumer group is so8400.

6.1 Non-Partitioned Destinations

The first two examples are for when the destination is not partitioned:

@SpringBootApplication
+public class ReRouteDlqApplication {
+
+    private static final String ORIGINAL_QUEUE = "so8400in.so8400";
+
+    private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
+
+    private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
+
+    private static final String X_RETRIES_HEADER = "x-retries";
+
+    public static void main(String[] args) throws Exception {
+        ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
+        System.out.println("Hit enter to terminate");
+        System.in.read();
+        context.close();
+    }
+
+    @Autowired
+    private RabbitTemplate rabbitTemplate;
+
+    @RabbitListener(queues = DLQ)
+    public void rePublish(Message failedMessage) {
+        Integer retriesHeader = (Integer) failedMessage.getMessageProperties().getHeaders().get(X_RETRIES_HEADER);
+        if (retriesHeader == null) {
+            retriesHeader = Integer.valueOf(0);
+        }
+        if (retriesHeader < 3) {
+            failedMessage.getMessageProperties().getHeaders().put(X_RETRIES_HEADER, retriesHeader + 1);
+            this.rabbitTemplate.send(ORIGINAL_QUEUE, failedMessage);
+        }
+        else {
+            this.rabbitTemplate.send(PARKING_LOT, failedMessage);
+        }
+    }
+
+    @Bean
+    public Queue parkingLot() {
+        return new Queue(PARKING_LOT);
+    }
+
+}
@SpringBootApplication
+public class ReRouteDlqApplication {
+
+    private static final String ORIGINAL_QUEUE = "so8400in.so8400";
+
+    private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
+
+    private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
+
+    private static final String X_RETRIES_HEADER = "x-retries";
+
+    private static final String DELAY_EXCHANGE = "dlqReRouter";
+
+    public static void main(String[] args) throws Exception {
+        ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
+        System.out.println("Hit enter to terminate");
+        System.in.read();
+        context.close();
+    }
+
+    @Autowired
+    private RabbitTemplate rabbitTemplate;
+
+    @RabbitListener(queues = DLQ)
+    public void rePublish(Message failedMessage) {
+        Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
+        Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
+        if (retriesHeader == null) {
+            retriesHeader = Integer.valueOf(0);
+        }
+        if (retriesHeader < 3) {
+            headers.put(X_RETRIES_HEADER, retriesHeader + 1);
+            headers.put("x-delay", 5000 * retriesHeader);
+            this.rabbitTemplate.send(DELAY_EXCHANGE, ORIGINAL_QUEUE, failedMessage);
+        }
+        else {
+            this.rabbitTemplate.send(PARKING_LOT, failedMessage);
+        }
+    }
+
+    @Bean
+    public DirectExchange delayExchange() {
+        DirectExchange exchange = new DirectExchange(DELAY_EXCHANGE);
+        exchange.setDelayed(true);
+        return exchange;
+    }
+
+    @Bean
+    public Binding bindOriginalToDelay() {
+        return BindingBuilder.bind(new Queue(ORIGINAL_QUEUE)).to(delayExchange()).with(ORIGINAL_QUEUE);
+    }
+
+    @Bean
+    public Queue parkingLot() {
+        return new Queue(PARKING_LOT);
+    }
+
+}

6.2 Partitioned Destinations

With partitioned destinations, there is one DLQ for all partitions. We determine the original queue from the headers.

6.2.1 republishToDlq=false

When republishToDlq is false, RabbitMQ publishes the message to the DLX/DLQ with an x-death header containing information about the original destination, as shown in the following example:

@SpringBootApplication
+public class ReRouteDlqApplication {
+
+	private static final String ORIGINAL_QUEUE = "so8400in.so8400";
+
+	private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
+
+	private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
+
+	private static final String X_DEATH_HEADER = "x-death";
+
+	private static final String X_RETRIES_HEADER = "x-retries";
+
+	public static void main(String[] args) throws Exception {
+		ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
+		System.out.println("Hit enter to terminate");
+		System.in.read();
+		context.close();
+	}
+
+	@Autowired
+	private RabbitTemplate rabbitTemplate;
+
+	@SuppressWarnings("unchecked")
+	@RabbitListener(queues = DLQ)
+	public void rePublish(Message failedMessage) {
+		Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
+		Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
+		if (retriesHeader == null) {
+			retriesHeader = Integer.valueOf(0);
+		}
+		if (retriesHeader < 3) {
+			headers.put(X_RETRIES_HEADER, retriesHeader + 1);
+			List<Map<String, ?>> xDeath = (List<Map<String, ?>>) headers.get(X_DEATH_HEADER);
+			String exchange = (String) xDeath.get(0).get("exchange");
+			List<String> routingKeys = (List<String>) xDeath.get(0).get("routing-keys");
+			this.rabbitTemplate.send(exchange, routingKeys.get(0), failedMessage);
+		}
+		else {
+			this.rabbitTemplate.send(PARKING_LOT, failedMessage);
+		}
+	}
+
+	@Bean
+	public Queue parkingLot() {
+		return new Queue(PARKING_LOT);
+	}
+
+}

6.2.2 republishToDlq=true

When republishToDlq is true, the republishing recoverer adds the original exchange and routing key to headers, as shown in the following example:

@SpringBootApplication
+public class ReRouteDlqApplication {
+
+	private static final String ORIGINAL_QUEUE = "so8400in.so8400";
+
+	private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
+
+	private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
+
+	private static final String X_RETRIES_HEADER = "x-retries";
+
+	private static final String X_ORIGINAL_EXCHANGE_HEADER = RepublishMessageRecoverer.X_ORIGINAL_EXCHANGE;
+
+	private static final String X_ORIGINAL_ROUTING_KEY_HEADER = RepublishMessageRecoverer.X_ORIGINAL_ROUTING_KEY;
+
+	public static void main(String[] args) throws Exception {
+		ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
+		System.out.println("Hit enter to terminate");
+		System.in.read();
+		context.close();
+	}
+
+	@Autowired
+	private RabbitTemplate rabbitTemplate;
+
+	@RabbitListener(queues = DLQ)
+	public void rePublish(Message failedMessage) {
+		Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
+		Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
+		if (retriesHeader == null) {
+			retriesHeader = Integer.valueOf(0);
+		}
+		if (retriesHeader < 3) {
+			headers.put(X_RETRIES_HEADER, retriesHeader + 1);
+			String exchange = (String) headers.get(X_ORIGINAL_EXCHANGE_HEADER);
+			String originalRoutingKey = (String) headers.get(X_ORIGINAL_ROUTING_KEY_HEADER);
+			this.rabbitTemplate.send(exchange, originalRoutingKey, failedMessage);
+		}
+		else {
+			this.rabbitTemplate.send(PARKING_LOT, failedMessage);
+		}
+	}
+
+	@Bean
+	public Queue parkingLot() {
+		return new Queue(PARKING_LOT);
+	}
+
+}
\ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_rabbit-error-channels.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_rabbit-error-channels.html new file mode 100644 index 00000000..87d8b8e0 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_rabbit-error-channels.html @@ -0,0 +1,6 @@ + + + 5. Error Channels

5. Error Channels

Starting with version 1.3, the binder unconditionally sends exceptions to an error channel for each consumer destination and can also be configured to send async producer send failures to an error channel. +See ??? for more information.

RabbitMQ has two types of send failures:

The latter is rare. +According to the RabbitMQ documentation "[A nack] will only be delivered if an internal error occurs in the Erlang process responsible for a queue.".

As well as enabling producer error channels (as described in ???), the RabbitMQ binder only sends messages to the channels if the connection factory is appropriately configured, as follows:

  • ccf.setPublisherConfirms(true);
  • ccf.setPublisherReturns(true);

When using Spring Boot configuration for the connection factory, set the following properties:

  • spring.rabbitmq.publisher-confirms
  • spring.rabbitmq.publisher-returns

The payload of the ErrorMessage for a returned message is a ReturnedAmqpMessageException with the following properties:

  • failedMessage: The spring-messaging Message<?> that failed to be sent.
  • amqpMessage: The raw spring-amqp Message.
  • replyCode: An integer value indicating the reason for the failure (for example, 312 - No route).
  • replyText: A text value indicating the reason for the failure (for example, NO_ROUTE).
  • exchange: The exchange to which the message was published.
  • routingKey: The routing key used when the message was published.

For negatively acknowledged confirmations, the payload is a NackedAmqpMessageException with the following properties:

  • failedMessage: The spring-messaging Message<?> that failed to be sent.
  • nackReason: A reason (if available — you may need to examine the broker logs for more information).

There is no automatic handling of these exceptions (such as sending to a dead-letter queue). +You can consume these exceptions with your own Spring Integration flow.

\ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_spring-cloud-stream-binder-rabbit.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_spring-cloud-stream-binder-rabbit.html new file mode 100644 index 00000000..b1ab5714 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/multi/multi_spring-cloud-stream-binder-rabbit.html @@ -0,0 +1,3 @@ + + + Spring Cloud Stream RabbitMQ Binder Reference Guide \ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/css/highlight.css b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/css/highlight.css new file mode 100644 index 00000000..3850f8b9 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/css/highlight.css @@ -0,0 +1,35 @@ +/* + code highlight CSS resemblign the Eclipse IDE default color schema + @author Costin Leau +*/ + +.hl-keyword { + color: #7F0055; + font-weight: bold; +} + +.hl-comment { + color: #3F5F5F; + font-style: italic; +} + +.hl-multiline-comment { + color: #3F5FBF; + font-style: italic; +} + +.hl-tag { + color: #3F7F7F; +} + +.hl-attribute { + color: #7F007F; +} + +.hl-value { + color: #2A00FF; +} + +.hl-string { + color: #2A00FF; +} \ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/css/manual-multipage.css b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/css/manual-multipage.css new file mode 100644 index 00000000..b790654b --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/css/manual-multipage.css @@ -0,0 +1,9 @@ +@IMPORT url("manual.css"); + +body.firstpage { + background: url("../images/background.png") no-repeat center top; +} + +div.part h1 { + border-top: none; +} diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/css/manual-singlepage.css b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/css/manual-singlepage.css new file mode 100644 index 00000000..303192a8 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/css/manual-singlepage.css @@ -0,0 +1,6 @@ +@IMPORT url("manual.css"); + +body { + background: url("../images/background.png") no-repeat center top; +} + diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/css/manual.css b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/css/manual.css new file mode 100644 index 00000000..20cf07da --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/css/manual.css @@ -0,0 +1,342 @@ +@IMPORT url("highlight.css"); + +html { + padding: 0pt; + margin: 0pt; +} + +body { + color: #333333; + margin: 15px 30px; + font-family: Helvetica, Arial, Freesans, Clean, Sans-serif; + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} + +code { + font-size: 16px; + font-family: Consolas, "Liberation Mono", Courier, monospace; +} + +:not(a) > code { + color: #6D180B; +} + +:not(pre) > code { + background-color: #F2F2F2; + border: 1px solid #CCCCCC; + border-radius: 4px; + padding: 1px 3px 0; + text-shadow: none; + white-space: nowrap; +} + +body > *:first-child { + margin-top: 0 !important; +} + +div { + margin: 0pt; +} + +hr { + border: 1px solid #CCCCCC; + background: #CCCCCC; +} + +h1, h2, h3, h4, h5, h6 { + color: #000000; + cursor: text; + font-weight: bold; + margin: 30px 0 10px; + padding: 0; +} + +h1, h2, h3 { + margin: 40px 0 10px; +} + +h1 { + margin: 70px 0 30px; + padding-top: 20px; +} + +div.part h1 { + border-top: 1px dotted #CCCCCC; +} + +h1, h1 code { + font-size: 32px; +} + +h2, h2 code { + font-size: 24px; +} + +h3, h3 code { + font-size: 20px; +} + +h4, h1 code, h5, h5 code, h6, h6 code { + font-size: 18px; +} + +div.book, div.chapter, div.appendix, div.part, div.preface { + min-width: 300px; + max-width: 1200px; + margin: 0 auto; +} + +p.releaseinfo { + font-weight: bold; + margin-bottom: 40px; + margin-top: 40px; +} + +div.authorgroup { + line-height: 1; +} + +p.copyright { + line-height: 1; + margin-bottom: -5px; +} + +.legalnotice p { + font-style: italic; + font-size: 14px; + line-height: 1; +} + +div.titlepage + p, div.titlepage + p { + margin-top: 0; +} + +pre { + line-height: 1.0; + color: black; +} + +a { + color: #4183C4; + text-decoration: none; +} + +p { + margin: 15px 0; + text-align: left; +} + +ul, ol { + padding-left: 30px; +} + +li p { + margin: 0; +} + +div.table { + margin: 1em; + padding: 0.5em; + text-align: center; +} + +div.table table, div.informaltable table { + display: table; + width: 100%; +} + +div.table td { + padding-left: 7px; + padding-right: 7px; +} + +.sidebar { + line-height: 1.4; + padding: 0 20px; + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; +} + +.sidebar p.title { + color: #6D180B; +} + +pre.programlisting, pre.screen { + font-size: 15px; + padding: 6px 10px; + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; + clear: both; + overflow: auto; + line-height: 1.4; + font-family: Consolas, "Liberation Mono", Courier, monospace; +} + +table { + border-collapse: collapse; + border-spacing: 0; + border: 1px solid #DDDDDD !important; + border-radius: 4px !important; + border-collapse: separate !important; + line-height: 1.6; +} + +table thead { + background: #F5F5F5; +} + +table tr { + border: none; + border-bottom: none; +} + +table th { + font-weight: bold; +} + +table th, table td { + border: none !important; + padding: 6px 13px; +} + +table tr:nth-child(2n) { + background-color: #F8F8F8; +} + +td p { + margin: 0 0 15px 0; +} + +div.table-contents td p { + margin: 0; +} + +div.important *, div.note *, div.tip *, div.warning *, div.navheader *, div.navfooter *, div.calloutlist * { + border: none !important; + background: none !important; + margin: 0; +} + +div.important p, div.note p, div.tip p, div.warning p { + color: #6F6F6F; + line-height: 1.6; +} + +div.important code, div.note code, div.tip code, div.warning code { + background-color: #F2F2F2 !important; + border: 1px solid #CCCCCC !important; + border-radius: 4px !important; + padding: 1px 3px 0 !important; + text-shadow: none !important; + white-space: nowrap !important; +} + +.note th, .tip th, .warning th { + display: none; +} + +.note tr:first-child td, .tip tr:first-child td, .warning tr:first-child td { + border-right: 1px solid #CCCCCC !important; + padding-top: 10px; +} + +div.calloutlist p, div.calloutlist td { + padding: 0; + margin: 0; +} + +div.calloutlist > table > tbody > tr > td:first-child { + padding-left: 10px; + width: 30px !important; +} + +div.important, div.note, div.tip, div.warning { + margin-left: 0px !important; + margin-right: 20px !important; + margin-top: 20px; + margin-bottom: 20px; + padding-top: 10px; + padding-bottom: 10px; +} + +div.toc { + line-height: 1.2; +} + +dl, dt { + margin-top: 1px; + margin-bottom: 0; +} + +div.toc > dl > dt { + font-size: 32px; + font-weight: bold; + margin: 30px 0 10px 0; + display: block; +} + +div.toc > dl > dd > dl > dt { + font-size: 24px; + font-weight: bold; + margin: 20px 0 10px 0; + display: block; +} + +div.toc > dl > dd > dl > dd > dl > dt { + font-weight: bold; + font-size: 20px; + margin: 10px 0 0 0; +} + +tbody.footnotes * { + border: none !important; +} + +div.footnote p { + margin: 0; + line-height: 1; +} + +div.footnote p sup { + margin-right: 6px; + vertical-align: middle; +} + +div.navheader { + border-bottom: 1px solid #CCCCCC; +} + +div.navfooter { + border-top: 1px solid #CCCCCC; +} + +.title { + margin-left: -1em; + padding-left: 1em; +} + +.title > a { + position: absolute; + visibility: hidden; + display: block; + font-size: 0.85em; + margin-top: 0.05em; + margin-left: -1em; + vertical-align: text-top; + color: black; +} + +.title > a:before { + content: "\00A7"; +} + +.title:hover > a, .title > a:hover, .title:hover > a:hover { + visibility: visible; +} + +.title:focus > a, .title > a:focus, .title:focus > a:focus { + outline: 0; +} diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/background.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/background.png new file mode 100644 index 00000000..15dca6fb Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/background.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/callouts/1.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/callouts/1.png new file mode 100644 index 00000000..7d473430 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/callouts/1.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/callouts/2.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/callouts/2.png new file mode 100644 index 00000000..5d09341b Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/callouts/2.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/callouts/3.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/callouts/3.png new file mode 100644 index 00000000..ef7b7004 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/callouts/3.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/caution.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/caution.png new file mode 100644 index 00000000..8a5e4fca Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/caution.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/important.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/important.png new file mode 100644 index 00000000..ec54df65 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/important.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/logo.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/logo.png new file mode 100644 index 00000000..ade2ce6e Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/logo.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/note.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/note.png new file mode 100644 index 00000000..88d997b1 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/note.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/tip.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/tip.png new file mode 100644 index 00000000..6530abb4 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/tip.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/warning.png b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/warning.png new file mode 100644 index 00000000..0d5b5244 Binary files /dev/null and b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/images/warning.png differ diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/spring-cloud-stream-binder-rabbit.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/spring-cloud-stream-binder-rabbit.html new file mode 100644 index 00000000..0843e406 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/single/spring-cloud-stream-binder-rabbit.html @@ -0,0 +1,496 @@ + + + Spring Cloud Stream RabbitMQ Binder Reference Guide

Spring Cloud Stream RabbitMQ Binder Reference Guide

Sabby Anandan, Marius Bogoevici, Eric Bottard, Mark Fisher, Ilayaperumal Gopinathan, Gunnar Hillert, Mark Pollack, Patrick Peralta, Glenn Renfro, Thomas Risberg, Dave Syer, David Turanski, Janne Valkealahti, Benjamin Klein, Gary Russell, Jay Bryant

Part I. Reference Guide

This guide describes the RabbitMQ implementation of the Spring Cloud Stream Binder. +It contains information about its design, usage and configuration options, as well as information on how the Stream Cloud Stream concepts map into RabbitMQ specific constructs.

1. Usage

To use the RabbitMQ binder, you can add it to your Spring Cloud Stream application, by using the following Maven coordinates:

<dependency>
+  <groupId>org.springframework.cloud</groupId>
+  <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
+</dependency>

Alternatively, you can use the Spring Cloud Stream RabbitMQ Starter, as follows:

<dependency>
+  <groupId>org.springframework.cloud</groupId>
+  <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
+</dependency>

2. RabbitMQ Binder Overview

The following simplified diagram shows how the RabbitMQ binder operates:

Figure 2.1. RabbitMQ Binder

rabbit binder

By default, the RabbitMQ Binder implementation maps each destination to a TopicExchange. +For each consumer group, a Queue is bound to that TopicExchange. +Each consumer instance has a corresponding RabbitMQ Consumer instance for its group’s Queue. +For partitioned producers and consumers, the queues are suffixed with the partition index and use the partition index as the routing key. +For anonymous consumers (those with no group property), an auto-delete queue (with a randomized unique name) is used.

By using the optional autoBindDlq option, you can configure the binder to create and configure dead-letter queues (DLQs) (and a dead-letter exchange DLX, as well as routing infrastructure). +By default, the dead letter queue has the name of the destination, appended with .dlq. +If retry is enabled (maxAttempts > 1), failed messages are delivered to the DLQ after retries are exhausted. +If retry is disabled (maxAttempts = 1), you should set requeueRejected to false (the default) so that failed messages are routed to the DLQ, instead of being re-queued. +In addition, republishToDlq causes the binder to publish a failed message to the DLQ (instead of rejecting it). +This feature lets additional information (such as the stack trace in the x-exception-stacktrace header) be added to the message in headers. +See the frameMaxHeadroom property for information about truncated stack traces. +This option does not need retry enabled. +You can republish a failed message after just one attempt. +Starting with version 1.2, you can configure the delivery mode of republished messages. +See the republishDeliveryMode property.

If the stream listener throws an ImmediateAcknowledgeAmqpException, the DLQ is bypassed and the message simply discarded. +Starting with version 2.1, this is true regardless of the setting of republishToDlq; previously it was only the case when republishToDlq was false.

[Important]Important

Setting requeueRejected to true (with republishToDlq=false ) causes the message to be re-queued and redelivered continually, which is likely not what you want unless the reason for the failure is transient. +In general, you should enable retry within the binder by setting maxAttempts to greater than one or by setting republishToDlq to true.

See Section 3.1, “RabbitMQ Binder Properties” for more information about these properties.

The framework does not provide any standard mechanism to consume dead-letter messages (or to re-route them back to the primary queue). +Some options are described in Chapter 6, Dead-Letter Queue Processing.

[Note]Note

When multiple RabbitMQ binders are used in a Spring Cloud Stream application, it is important to disable 'RabbitAutoConfiguration' to avoid the same configuration from RabbitAutoConfiguration being applied to the two binders. +You can exclude the class by using the @SpringBootApplication annotation.

Starting with version 2.0, the RabbitMessageChannelBinder sets the RabbitTemplate.userPublisherConnection property to true so that the non-transactional producers avoid deadlocks on consumers, which can happen if cached connections are blocked because of a memory alarm on the broker.

[Note]Note

Currently, a multiplex consumer (a single consumer listening to multiple queues) is only supported for message-driven conssumers; polled consumers can only retrieve messages from a single queue.

3. Configuration Options

This section contains settings specific to the RabbitMQ Binder and bound channels.

For general binding configuration options and properties, see the Spring Cloud Stream core documentation.

3.1 RabbitMQ Binder Properties

By default, the RabbitMQ binder uses Spring Boot’s ConnectionFactory. +Conseuqently, it supports all Spring Boot configuration options for RabbitMQ. +(For reference, see the Spring Boot documentation). +RabbitMQ configuration options use the spring.rabbitmq prefix.

In addition to Spring Boot options, the RabbitMQ binder supports the following properties:

spring.cloud.stream.rabbit.binder.adminAddresses

A comma-separated list of RabbitMQ management plugin URLs. +Only used when nodes contains more than one entry. +Each entry in this list must have a corresponding entry in spring.rabbitmq.addresses. +Only needed if you use a RabbitMQ cluster and wish to consume from the node that hosts the queue. +See Queue Affinity and the LocalizedQueueConnectionFactory for more information.

Default: empty.

spring.cloud.stream.rabbit.binder.nodes

A comma-separated list of RabbitMQ node names. +When more than one entry, used to locate the server address where a queue is located. +Each entry in this list must have a corresponding entry in spring.rabbitmq.addresses. +Only needed if you use a RabbitMQ cluster and wish to consume from the node that hosts the queue. +See Queue Affinity and the LocalizedQueueConnectionFactory for more information.

Default: empty.

spring.cloud.stream.rabbit.binder.compressionLevel

The compression level for compressed bindings. +See java.util.zip.Deflater.

Default: 1 (BEST_LEVEL).

spring.cloud.stream.binder.connection-name-prefix

A connection name prefix used to name the connection(s) created by this binder. +The name is this prefix followed by #n, where n increments each time a new connection is opened.

Default: none (Spring AMQP default).

3.2 RabbitMQ Consumer Properties

The following properties are available for Rabbit consumers only and must be prefixed with spring.cloud.stream.rabbit.bindings.<channelName>.consumer..

acknowledgeMode

The acknowledge mode.

Default: AUTO.

anonymousGroupPrefix

When the binding has no group property, an anonymous, auto-delete queue is bound to the destination exchange. +The default naming stragegy for such queues results in a queue named anonymous.<base64 representation of a UUID>. +Set this property to change the prefix to something other than the default.

Default: anonymous..

autoBindDlq

Whether to automatically declare the DLQ and bind it to the binder DLX.

Default: false.

bindingRoutingKey

The routing key with which to bind the queue to the exchange (if bindQueue is true). +For partitioned destinations, -<instanceIndex> is appended.

Default: #.

bindQueue

Whether to declare the queue and bind it to the destination exchange. +Set it to false if you have set up your own infrastructure and have previously created and bound the queue.

Default: true.

consumerTagPrefix

Used to create the consumer tag(s); will be appended by #n where n increments for each consumer created. +Example: ${spring.application.name}-${spring.cloud.stream.bindings.input.group}-${spring.cloud.stream.instance-index}.

Default: none - the broker will generate random consumer tags.

containerType

Select the type of listener container to be used. +See Choosing a Container in the Spring AMQP documentation for more information.

Default: simple

deadLetterQueueName

The name of the DLQ

Default: prefix+destination.dlq

deadLetterExchange

A DLX to assign to the queue. +Relevant only if autoBindDlq is true.

Default: 'prefix+DLX'

deadLetterExchangeType

The type of the DLX to assign to the queue. +Relevant only if autoBindDlq is true.

Default: 'direct'

deadLetterRoutingKey

A dead letter routing key to assign to the queue. +Relevant only if autoBindDlq is true.

Default: destination

declareDlx

Whether to declare the dead letter exchange for the destination. +Relevant only if autoBindDlq is true. +Set to false if you have a pre-configured DLX.

Default: true.

declareExchange

Whether to declare the exchange for the destination.

Default: true.

delayedExchange

Whether to declare the exchange as a Delayed Message Exchange. +Requires the delayed message exchange plugin on the broker. +The x-delayed-type argument is set to the exchangeType.

Default: false.

dlqDeadLetterExchange

If a DLQ is declared, a DLX to assign to that queue.

Default: none

dlqDeadLetterRoutingKey

If a DLQ is declared, a dead letter routing key to assign to that queue.

Default: none

dlqExpires

How long before an unused dead letter queue is deleted (in milliseconds).

Default: no expiration

dlqLazy

Declare the dead letter queue with the x-queue-mode=lazy argument. +See Lazy Queues. +Consider using a policy instead of this setting, because using a policy allows changing the setting without deleting the queue.

Default: false.

dlqMaxLength

Maximum number of messages in the dead letter queue.

Default: no limit

dlqMaxLengthBytes

Maximum number of total bytes in the dead letter queue from all messages.

Default: no limit

dlqMaxPriority

Maximum priority of messages in the dead letter queue (0-255).

Default: none

dlqOverflowBehavior

Action to take when dlqMaxLength or dlqMaxLengthBytes is exceeded; currently drop-head or reject-publish but refer to the RabbitMQ documentation.

Default: none

dlqTtl

Default time to live to apply to the dead letter queue when declared (in milliseconds).

Default: no limit

durableSubscription

Whether the subscription should be durable. +Only effective if group is also set.

Default: true.

exchangeAutoDelete

If declareExchange is true, whether the exchange should be auto-deleted (that is, removed after the last queue is removed).

Default: true.

exchangeDurable

If declareExchange is true, whether the exchange should be durable (that is, it survives broker restart).

Default: true.

exchangeType

The exchange type: direct, fanout or topic for non-partitioned destinations and direct or topic for partitioned destinations.

Default: topic.

exclusive

Whether to create an exclusive consumer. +Concurrency should be 1 when this is true. +Often used when strict ordering is required but enabling a hot standby instance to take over after a failure. +See recoveryInterval, which controls how often a standby instance attempts to consume.

Default: false.

expires

How long before an unused queue is deleted (in milliseconds).

Default: no expiration

failedDeclarationRetryInterval

The interval (in milliseconds) between attempts to consume from a queue if it is missing.

Default: 5000

frameMaxHeadroom

The number of bytes to reserve for other headers when adding the stack trace to a DLQ message header. +All headers must fit within the frame_max size configured on the broker. +Stack traces can be large; if the size plus this property exceeds frame_max then the stack trace will be truncated. +A WARN log will be written; consider increasing the frame_max or reducing the stack trace by catching the exception and throwing one with a smaller stack trace.

Default: 20000

headerPatterns

Patterns for headers to be mapped from inbound messages.

Default: ['*'] (all headers).

lazy

Declare the queue with the x-queue-mode=lazy argument. +See Lazy Queues. +Consider using a policy instead of this setting, because using a policy allows changing the setting without deleting the queue.

Default: false.

maxConcurrency

The maximum number of consumers. +Not supported when the containerType is direct.

Default: 1.

maxLength

The maximum number of messages in the queue.

Default: no limit

maxLengthBytes

The maximum number of total bytes in the queue from all messages.

Default: no limit

maxPriority

The maximum priority of messages in the queue (0-255).

Default: none

missingQueuesFatal

When the queue cannot be found, whether to treat the condition as fatal and stop the listener container. +Defaults to false so that the container keeps trying to consume from the queue — for example, when using a cluster and the node hosting a non-HA queue is down.

Default: false

overflowBehavior

Action to take when maxLength or maxLengthBytes is exceeded; currently drop-head or reject-publish but refer to the RabbitMQ documentation.

Default: none

prefetch

Prefetch count.

Default: 1.

prefix

A prefix to be added to the name of the destination and queues.

Default: "".

queueDeclarationRetries

The number of times to retry consuming from a queue if it is missing. +Relevant only when missingQueuesFatal is true. +Otherwise, the container keeps retrying indefinitely. +Not supported when the containerType is direct.

Default: 3

queueNameGroupOnly

When true, consume from a queue with a name equal to the group. +Otherwise the queue name is destination.group. +This is useful, for example, when using Spring Cloud Stream to consume from an existing RabbitMQ queue.

Default: false.

recoveryInterval

The interval between connection recovery attempts, in milliseconds.

Default: 5000.

requeueRejected

Whether delivery failures should be re-queued when retry is disabled or republishToDlq is false.

Default: false.

republishDeliveryMode

When republishToDlq is true, specifies the delivery mode of the republished message.

Default: DeliveryMode.PERSISTENT

republishToDlq

By default, messages that fail after retries are exhausted are rejected. +If a dead-letter queue (DLQ) is configured, RabbitMQ routes the failed message (unchanged) to the DLQ. +If set to true, the binder republishs failed messages to the DLQ with additional headers, including the exception message and stack trace from the cause of the final failure. +Also see the frameMaxHeadroom property.

Default: false

transacted

Whether to use transacted channels.

Default: false.

ttl

Default time to live to apply to the queue when declared (in milliseconds).

Default: no limit

txSize

The number of deliveries between acks. +Not supported when the containerType is direct.

Default: 1.

3.3 Advanced Listener Container Configuration

To set listener container properties that are not exposed as binder or binding properties, add a single bean of type ListenerContainerCustomizer to the application context. +The binder and binding properties will be set and then the customizer will be called. +The customizer (configure() method) is provided with the queue name as well as the consumer group as arguments.

3.4 Rabbit Producer Properties

The following properties are available for Rabbit producers only and +must be prefixed with spring.cloud.stream.rabbit.bindings.<channelName>.producer..

autoBindDlq

Whether to automatically declare the DLQ and bind it to the binder DLX.

Default: false.

batchingEnabled

Whether to enable message batching by producers. +Messages are batched into one message according to the following properties (described in the next three entries in this list): 'batchSize', batchBufferLimit, and batchTimeout. +See Batching for more information.

Default: false.

batchSize

The number of messages to buffer when batching is enabled.

Default: 100.

batchBufferLimit

The maximum buffer size when batching is enabled.

Default: 10000.

batchTimeout

The batch timeout when batching is enabled.

Default: 5000.

bindingRoutingKey

The routing key with which to bind the queue to the exchange (if bindQueue is true). +Only applies to non-partitioned destinations. +Only applies if requiredGroups are provided and then only to those groups.

Default: #.

bindQueue

Whether to declare the queue and bind it to the destination exchange. +Set it to false if you have set up your own infrastructure and have previously created and bound the queue. +Only applies if requiredGroups are provided and then only to those groups.

Default: true.

compress

Whether data should be compressed when sent.

Default: false.

confirmAckChannel

When errorChannelEnabled is true, a channel to which to send positive delivery acknowledgments (aka publisher confirms). +If the channel does not exist, a DirectChannel is registered with this name. +The connection factory must be configured to enable publisher confirms.

Default: nullChannel (acks are discarded).

deadLetterQueueName

The name of the DLQ +Only applies if requiredGroups are provided and then only to those groups.

Default: prefix+destination.dlq

deadLetterExchange

A DLX to assign to the queue. +Relevant only when autoBindDlq is true. +Applies only when requiredGroups are provided and then only to those groups.

Default: 'prefix+DLX'

deadLetterExchangeType

The type of the DLX to assign to the queue. +Relevant only if autoBindDlq is true. +Applies only when requiredGroups are provided and then only to those groups.

Default: 'direct'

deadLetterRoutingKey

A dead letter routing key to assign to the queue. +Relevant only when autoBindDlq is true. +Applies only when requiredGroups are provided and then only to those groups.

Default: destination

declareDlx

Whether to declare the dead letter exchange for the destination. +Relevant only if autoBindDlq is true. +Set to false if you have a pre-configured DLX. +Applies only when requiredGroups are provided and then only to those groups.

Default: true.

declareExchange

Whether to declare the exchange for the destination.

Default: true.

delayExpression

A SpEL expression to evaluate the delay to apply to the message (x-delay header). +It has no effect if the exchange is not a delayed message exchange.

Default: No x-delay header is set.

delayedExchange

Whether to declare the exchange as a Delayed Message Exchange. +Requires the delayed message exchange plugin on the broker. +The x-delayed-type argument is set to the exchangeType.

Default: false.

deliveryMode

The delivery mode.

Default: PERSISTENT.

dlqDeadLetterExchange

When a DLQ is declared, a DLX to assign to that queue. +Applies only if requiredGroups are provided and then only to those groups.

Default: none

dlqDeadLetterRoutingKey

When a DLQ is declared, a dead letter routing key to assign to that queue. +Applies only when requiredGroups are provided and then only to those groups.

Default: none

dlqExpires

How long (in milliseconds) before an unused dead letter queue is deleted. +Applies only when requiredGroups are provided and then only to those groups.

Default: no expiration

dlqLazy
Declare the dead letter queue with the x-queue-mode=lazy argument. +See Lazy Queues. +Consider using a policy instead of this setting, because using a policy allows changing the setting without deleting the queue. +Applies only when requiredGroups are provided and then only to those groups.
dlqMaxLength

Maximum number of messages in the dead letter queue. +Applies only if requiredGroups are provided and then only to those groups.

Default: no limit

dlqMaxLengthBytes

Maximum number of total bytes in the dead letter queue from all messages. +Applies only when requiredGroups are provided and then only to those groups.

Default: no limit

dlqMaxPriority

Maximum priority of messages in the dead letter queue (0-255) +Applies only when requiredGroups are provided and then only to those groups.

Default: none

dlqTtl

Default time (in milliseconds) to live to apply to the dead letter queue when declared. +Applies only when requiredGroups are provided and then only to those groups.

Default: no limit

exchangeAutoDelete

If declareExchange is true, whether the exchange should be auto-delete (it is removed after the last queue is removed).

Default: true.

exchangeDurable

If declareExchange is true, whether the exchange should be durable (survives broker restart).

Default: true.

exchangeType

The exchange type: direct, fanout or topic for non-partitioned destinations and direct or topic for partitioned destinations.

Default: topic.

expires

How long (in milliseconds) before an unused queue is deleted. +Applies only when requiredGroups are provided and then only to those groups.

Default: no expiration

headerPatterns

Patterns for headers to be mapped to outbound messages.

Default: ['*'] (all headers).

lazy

Declare the queue with the x-queue-mode=lazy argument. +See Lazy Queues. +Consider using a policy instead of this setting, because using a policy allows changing the setting without deleting the queue. +Applies only when requiredGroups are provided and then only to those groups.

Default: false.

maxLength

Maximum number of messages in the queue. +Applies only when requiredGroups are provided and then only to those groups.

Default: no limit

maxLengthBytes

Maximum number of total bytes in the queue from all messages. +Only applies if requiredGroups are provided and then only to those groups.

Default: no limit

maxPriority

Maximum priority of messages in the queue (0-255). +Only applies if requiredGroups are provided and then only to those groups.

Default: none

prefix

A prefix to be added to the name of the destination exchange.

Default: "".

queueNameGroupOnly

When true, consume from a queue with a name equal to the group. +Otherwise the queue name is destination.group. +This is useful, for example, when using Spring Cloud Stream to consume from an existing RabbitMQ queue. +Applies only when requiredGroups are provided and then only to those groups.

Default: false.

routingKeyExpression

A SpEL expression to determine the routing key to use when publishing messages. +For a fixed routing key, use a literal expression, such as routingKeyExpression='my.routingKey' in a properties file or routingKeyExpression: '''my.routingKey''' in a YAML file.

Default: destination or destination-<partition> for partitioned destinations.

transacted

Whether to use transacted channels.

Default: false.

ttl

Default time (in milliseconds) to live to apply to the queue when declared. +Applies only when requiredGroups are provided and then only to those groups.

Default: no limit

[Note]Note

In the case of RabbitMQ, content type headers can be set by external applications. +Spring Cloud Stream supports them as part of an extended internal protocol used for any type of transport — including transports, such as Kafka (prior to 0.11), that do not natively support headers.

4. Retry With the RabbitMQ Binder

When retry is enabled within the binder, the listener container thread is suspended for any back off periods that are configured. +This might be important when strict ordering is required with a single consumer. However, for other use cases, it prevents other messages from being processed on that thread. +An alternative to using binder retry is to set up dead lettering with time to live on the dead-letter queue (DLQ) as well as dead-letter configuration on the DLQ itself. +See Section 3.1, “RabbitMQ Binder Properties” for more information about the properties discussed here. +You can use the following example configuration to enable this feature:

  • Set autoBindDlq to true. +The binder create a DLQ. +Optionally, you can specify a name in deadLetterQueueName.
  • Set dlqTtl to the back off time you want to wait between redeliveries.
  • Set the dlqDeadLetterExchange to the default exchange. +Expired messages from the DLQ are routed to the original queue, because the default deadLetterRoutingKey is the queue name (destination.group). +Setting to the default exchange is achieved by setting the property with no value, as shown in the next example.

To force a message to be dead-lettered, either throw an AmqpRejectAndDontRequeueException or set requeueRejected to true (the default) and throw any exception.

The loop continue without end, which is fine for transient problems, but you may want to give up after some number of attempts. +Fortunately, RabbitMQ provides the x-death header, which lets you determine how many cycles have occurred.

To acknowledge a message after giving up, throw an ImmediateAcknowledgeAmqpException.

4.1 Putting it All Together

The following configuration creates an exchange myDestination with queue myDestination.consumerGroup bound to a topic exchange with a wildcard routing key #:

---
+spring.cloud.stream.bindings.input.destination=myDestination
+spring.cloud.stream.bindings.input.group=consumerGroup
+#disable binder retries
+spring.cloud.stream.bindings.input.consumer.max-attempts=1
+#dlx/dlq setup
+spring.cloud.stream.rabbit.bindings.input.consumer.auto-bind-dlq=true
+spring.cloud.stream.rabbit.bindings.input.consumer.dlq-ttl=5000
+spring.cloud.stream.rabbit.bindings.input.consumer.dlq-dead-letter-exchange=
+---

This configuration creates a DLQ bound to a direct exchange (DLX) with a routing key of myDestination.consumerGroup. +When messages are rejected, they are routed to the DLQ. +After 5 seconds, the message expires and is routed to the original queue by using the queue name as the routing key, as shown in the following example:

Spring Boot application.  +

@SpringBootApplication
+@EnableBinding(Sink.class)
+public class XDeathApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(XDeathApplication.class, args);
+    }
+
+    @StreamListener(Sink.INPUT)
+    public void listen(String in, @Header(name = "x-death", required = false) Map<?,?> death) {
+        if (death != null && death.get("count").equals(3L)) {
+            // giving up - don't send to DLX
+            throw new ImmediateAcknowledgeAmqpException("Failed after 4 attempts");
+        }
+        throw new AmqpRejectAndDontRequeueException("failed");
+    }
+
+}

+

Notice that the count property in the x-death header is a Long.

5. Error Channels

Starting with version 1.3, the binder unconditionally sends exceptions to an error channel for each consumer destination and can also be configured to send async producer send failures to an error channel. +See ??? for more information.

RabbitMQ has two types of send failures:

The latter is rare. +According to the RabbitMQ documentation "[A nack] will only be delivered if an internal error occurs in the Erlang process responsible for a queue.".

As well as enabling producer error channels (as described in ???), the RabbitMQ binder only sends messages to the channels if the connection factory is appropriately configured, as follows:

  • ccf.setPublisherConfirms(true);
  • ccf.setPublisherReturns(true);

When using Spring Boot configuration for the connection factory, set the following properties:

  • spring.rabbitmq.publisher-confirms
  • spring.rabbitmq.publisher-returns

The payload of the ErrorMessage for a returned message is a ReturnedAmqpMessageException with the following properties:

  • failedMessage: The spring-messaging Message<?> that failed to be sent.
  • amqpMessage: The raw spring-amqp Message.
  • replyCode: An integer value indicating the reason for the failure (for example, 312 - No route).
  • replyText: A text value indicating the reason for the failure (for example, NO_ROUTE).
  • exchange: The exchange to which the message was published.
  • routingKey: The routing key used when the message was published.

For negatively acknowledged confirmations, the payload is a NackedAmqpMessageException with the following properties:

  • failedMessage: The spring-messaging Message<?> that failed to be sent.
  • nackReason: A reason (if available — you may need to examine the broker logs for more information).

There is no automatic handling of these exceptions (such as sending to a dead-letter queue). +You can consume these exceptions with your own Spring Integration flow.

6. Dead-Letter Queue Processing

Because you cannot anticipate how users would want to dispose of dead-lettered messages, the framework does not provide any standard mechanism to handle them. +If the reason for the dead-lettering is transient, you may wish to route the messages back to the original queue. +However, if the problem is a permanent issue, that could cause an infinite loop. +The following Spring Boot application shows an example of how to route those messages back to the original queue but moves them to a third parking lot queue after three attempts. +The second example uses the RabbitMQ Delayed Message Exchange to introduce a delay to the re-queued message. +In this example, the delay increases for each attempt. +These examples use a @RabbitListener to receive messages from the DLQ. +You could also use RabbitTemplate.receive() in a batch process.

The examples assume the original destination is so8400in and the consumer group is so8400.

6.1 Non-Partitioned Destinations

The first two examples are for when the destination is not partitioned:

@SpringBootApplication
+public class ReRouteDlqApplication {
+
+    private static final String ORIGINAL_QUEUE = "so8400in.so8400";
+
+    private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
+
+    private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
+
+    private static final String X_RETRIES_HEADER = "x-retries";
+
+    public static void main(String[] args) throws Exception {
+        ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
+        System.out.println("Hit enter to terminate");
+        System.in.read();
+        context.close();
+    }
+
+    @Autowired
+    private RabbitTemplate rabbitTemplate;
+
+    @RabbitListener(queues = DLQ)
+    public void rePublish(Message failedMessage) {
+        Integer retriesHeader = (Integer) failedMessage.getMessageProperties().getHeaders().get(X_RETRIES_HEADER);
+        if (retriesHeader == null) {
+            retriesHeader = Integer.valueOf(0);
+        }
+        if (retriesHeader < 3) {
+            failedMessage.getMessageProperties().getHeaders().put(X_RETRIES_HEADER, retriesHeader + 1);
+            this.rabbitTemplate.send(ORIGINAL_QUEUE, failedMessage);
+        }
+        else {
+            this.rabbitTemplate.send(PARKING_LOT, failedMessage);
+        }
+    }
+
+    @Bean
+    public Queue parkingLot() {
+        return new Queue(PARKING_LOT);
+    }
+
+}
@SpringBootApplication
+public class ReRouteDlqApplication {
+
+    private static final String ORIGINAL_QUEUE = "so8400in.so8400";
+
+    private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
+
+    private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
+
+    private static final String X_RETRIES_HEADER = "x-retries";
+
+    private static final String DELAY_EXCHANGE = "dlqReRouter";
+
+    public static void main(String[] args) throws Exception {
+        ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
+        System.out.println("Hit enter to terminate");
+        System.in.read();
+        context.close();
+    }
+
+    @Autowired
+    private RabbitTemplate rabbitTemplate;
+
+    @RabbitListener(queues = DLQ)
+    public void rePublish(Message failedMessage) {
+        Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
+        Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
+        if (retriesHeader == null) {
+            retriesHeader = Integer.valueOf(0);
+        }
+        if (retriesHeader < 3) {
+            headers.put(X_RETRIES_HEADER, retriesHeader + 1);
+            headers.put("x-delay", 5000 * retriesHeader);
+            this.rabbitTemplate.send(DELAY_EXCHANGE, ORIGINAL_QUEUE, failedMessage);
+        }
+        else {
+            this.rabbitTemplate.send(PARKING_LOT, failedMessage);
+        }
+    }
+
+    @Bean
+    public DirectExchange delayExchange() {
+        DirectExchange exchange = new DirectExchange(DELAY_EXCHANGE);
+        exchange.setDelayed(true);
+        return exchange;
+    }
+
+    @Bean
+    public Binding bindOriginalToDelay() {
+        return BindingBuilder.bind(new Queue(ORIGINAL_QUEUE)).to(delayExchange()).with(ORIGINAL_QUEUE);
+    }
+
+    @Bean
+    public Queue parkingLot() {
+        return new Queue(PARKING_LOT);
+    }
+
+}

6.2 Partitioned Destinations

With partitioned destinations, there is one DLQ for all partitions. We determine the original queue from the headers.

6.2.1 republishToDlq=false

When republishToDlq is false, RabbitMQ publishes the message to the DLX/DLQ with an x-death header containing information about the original destination, as shown in the following example:

@SpringBootApplication
+public class ReRouteDlqApplication {
+
+	private static final String ORIGINAL_QUEUE = "so8400in.so8400";
+
+	private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
+
+	private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
+
+	private static final String X_DEATH_HEADER = "x-death";
+
+	private static final String X_RETRIES_HEADER = "x-retries";
+
+	public static void main(String[] args) throws Exception {
+		ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
+		System.out.println("Hit enter to terminate");
+		System.in.read();
+		context.close();
+	}
+
+	@Autowired
+	private RabbitTemplate rabbitTemplate;
+
+	@SuppressWarnings("unchecked")
+	@RabbitListener(queues = DLQ)
+	public void rePublish(Message failedMessage) {
+		Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
+		Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
+		if (retriesHeader == null) {
+			retriesHeader = Integer.valueOf(0);
+		}
+		if (retriesHeader < 3) {
+			headers.put(X_RETRIES_HEADER, retriesHeader + 1);
+			List<Map<String, ?>> xDeath = (List<Map<String, ?>>) headers.get(X_DEATH_HEADER);
+			String exchange = (String) xDeath.get(0).get("exchange");
+			List<String> routingKeys = (List<String>) xDeath.get(0).get("routing-keys");
+			this.rabbitTemplate.send(exchange, routingKeys.get(0), failedMessage);
+		}
+		else {
+			this.rabbitTemplate.send(PARKING_LOT, failedMessage);
+		}
+	}
+
+	@Bean
+	public Queue parkingLot() {
+		return new Queue(PARKING_LOT);
+	}
+
+}

6.2.2 republishToDlq=true

When republishToDlq is true, the republishing recoverer adds the original exchange and routing key to headers, as shown in the following example:

@SpringBootApplication
+public class ReRouteDlqApplication {
+
+	private static final String ORIGINAL_QUEUE = "so8400in.so8400";
+
+	private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
+
+	private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
+
+	private static final String X_RETRIES_HEADER = "x-retries";
+
+	private static final String X_ORIGINAL_EXCHANGE_HEADER = RepublishMessageRecoverer.X_ORIGINAL_EXCHANGE;
+
+	private static final String X_ORIGINAL_ROUTING_KEY_HEADER = RepublishMessageRecoverer.X_ORIGINAL_ROUTING_KEY;
+
+	public static void main(String[] args) throws Exception {
+		ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
+		System.out.println("Hit enter to terminate");
+		System.in.read();
+		context.close();
+	}
+
+	@Autowired
+	private RabbitTemplate rabbitTemplate;
+
+	@RabbitListener(queues = DLQ)
+	public void rePublish(Message failedMessage) {
+		Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
+		Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
+		if (retriesHeader == null) {
+			retriesHeader = Integer.valueOf(0);
+		}
+		if (retriesHeader < 3) {
+			headers.put(X_RETRIES_HEADER, retriesHeader + 1);
+			String exchange = (String) headers.get(X_ORIGINAL_EXCHANGE_HEADER);
+			String originalRoutingKey = (String) headers.get(X_ORIGINAL_ROUTING_KEY_HEADER);
+			this.rabbitTemplate.send(exchange, originalRoutingKey, failedMessage);
+		}
+		else {
+			this.rabbitTemplate.send(PARKING_LOT, failedMessage);
+		}
+	}
+
+	@Bean
+	public Queue parkingLot() {
+		return new Queue(PARKING_LOT);
+	}
+
+}

7. Partitioning with the RabbitMQ Binder

RabbitMQ does not support partitioning natively.

Sometimes, it is advantageous to send data to specific partitions — for example, when you want to strictly order message processing, all messages for a particular customer should go to the same partition.

The RabbitMessageChannelBinder provides partitioning by binding a queue for each partition to the destination exchange.

The following Java and YAML examples show how to configure the producer:

Producer.  +

@SpringBootApplication
+@EnableBinding(Source.class)
+public class RabbitPartitionProducerApplication {
+
+    private static final Random RANDOM = new Random(System.currentTimeMillis());
+
+    private static final String[] data = new String[] {
+            "abc1", "def1", "qux1",
+            "abc2", "def2", "qux2",
+            "abc3", "def3", "qux3",
+            "abc4", "def4", "qux4",
+            };
+
+    public static void main(String[] args) {
+        new SpringApplicationBuilder(RabbitPartitionProducerApplication.class)
+            .web(false)
+            .run(args);
+    }
+
+    @InboundChannelAdapter(channel = Source.OUTPUT, poller = @Poller(fixedRate = "5000"))
+    public Message<?> generate() {
+        String value = data[RANDOM.nextInt(data.length)];
+        System.out.println("Sending: " + value);
+        return MessageBuilder.withPayload(value)
+                .setHeader("partitionKey", value)
+                .build();
+    }
+
+}

+

application.yml.  +

    spring:
+      cloud:
+        stream:
+          bindings:
+            output:
+              destination: partitioned.destination
+              producer:
+                partitioned: true
+                partition-key-expression: headers['partitionKey']
+                partition-count: 2
+                required-groups:
+                - myGroup

+

[Note]Note

The configuration in the prececing example uses the default partitioning (key.hashCode() % partitionCount). +This may or may not provide a suitably balanced algorithm, depending on the key values. +You can override this default by using the partitionSelectorExpression or partitionSelectorClass properties.

The required-groups property is required only if you need the consumer queues to be provisioned when the producer is deployed. +Otherwise, any messages sent to a partition are lost until the corresponding consumer is deployed.

The following configuration provisions a topic exchange:

part exchange

The following queues are bound to that exchange:

part queues

The following bindings associate the queues to the exchange:

part bindings

The following Java and YAML examples continue the previous examples and show how to configure the consumer:

Consumer.  +

@SpringBootApplication
+@EnableBinding(Sink.class)
+public class RabbitPartitionConsumerApplication {
+
+    public static void main(String[] args) {
+        new SpringApplicationBuilder(RabbitPartitionConsumerApplication.class)
+            .web(false)
+            .run(args);
+    }
+
+    @StreamListener(Sink.INPUT)
+    public void listen(@Payload String in, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue) {
+        System.out.println(in + " received from queue " + queue);
+    }
+
+}

+

application.yml.  +

    spring:
+      cloud:
+        stream:
+          bindings:
+            input:
+              destination: partitioned.destination
+              group: myGroup
+              consumer:
+                partitioned: true
+                instance-index: 0

+

[Important]Important

The RabbitMessageChannelBinder does not support dynamic scaling. +There must be at least one consumer per partition. +The consumer’s instanceIndex is used to indicate which partition is consumed. +Platforms such as Cloud Foundry can have only one instance with an instanceIndex.

Part II. Appendices

Appendix A. Building

A.1 Basic Compile and Test

To build the source you will need to install JDK 1.8.

The build uses the Maven wrapper so you don’t have to install a specific +version of Maven. To enable the tests, you should have RabbitMQ server running +on localhost and the default port (5672) +before building.

The main build command is

$ ./mvnw clean install

You can also add '-DskipTests' if you like, to avoid running the tests.

[Note]Note

You can also install Maven (>=3.3.3) yourself and run the mvn command +in place of ./mvnw in the examples below. If you do that you also +might need to add -P spring if your local Maven settings do not +contain repository declarations for spring pre-release artifacts.

[Note]Note

Be aware that you might need to increase the amount of memory +available to Maven by setting a MAVEN_OPTS environment variable with +a value like -Xmx512m -XX:MaxPermSize=128m. We try to cover this in +the .mvn configuration, so if you find you have to do it to make a +build succeed, please raise a ticket to get the settings added to +source control.

The projects that require middleware generally include a +docker-compose.yml, so consider using +Docker Compose to run the middeware servers +in Docker containers.

A.2 Documentation

There is a "full" profile that will generate documentation.

A.3 Working with the code

If you don’t have an IDE preference we would recommend that you use +Spring Tools Suite or +Eclipse when working with the code. We use the +m2eclipe eclipse plugin for maven support. Other IDEs and tools +should also work without issue.

A.3.1 Importing into eclipse with m2eclipse

We recommend the m2eclipe eclipse plugin when working with +eclipse. If you don’t already have m2eclipse installed it is available from the "eclipse +marketplace".

Unfortunately m2e does not yet support Maven 3.3, so once the projects +are imported into Eclipse you will also need to tell m2eclipse to use +the .settings.xml file for the projects. If you do not do this you +may see many different errors related to the POMs in the +projects. Open your Eclipse preferences, expand the Maven +preferences, and select User Settings. In the User Settings field +click Browse and navigate to the Spring Cloud project you imported +selecting the .settings.xml file in that project. Click Apply and +then OK to save the preference changes.

[Note]Note

Alternatively you can copy the repository settings from .settings.xml into your own ~/.m2/settings.xml.

A.3.2 Importing into eclipse without m2eclipse

If you prefer not to use m2eclipse you can generate eclipse project metadata using the +following command:

$ ./mvnw eclipse:eclipse

The generated eclipse projects can be imported by selecting import existing projects +from the file menu.

Appendix B. Contributing

Spring Cloud is released under the non-restrictive Apache 2.0 license, +and follows a very standard Github development process, using Github +tracker for issues and merging pull requests into master. If you want +to contribute even something trivial please do not hesitate, but +follow the guidelines below.

B.1 Sign the Contributor License Agreement

Before we accept a non-trivial patch or pull request we will need you to sign the +contributor’s agreement. +Signing the contributor’s agreement does not grant anyone commit rights to the main +repository, but it does mean that we can accept your contributions, and you will get an +author credit if we do. Active contributors might be asked to join the core team, and +given the ability to merge pull requests.

B.2 Code Conventions and Housekeeping

None of these is essential for a pull request, but they will all help. They can also be +added after the original pull request but before a merge.

  • Use the Spring Framework code format conventions. If you use Eclipse +you can import formatter settings using the +eclipse-code-formatter.xml file from the +Spring +Cloud Build project. If using IntelliJ, you can use the +Eclipse Code Formatter +Plugin to import the same file.
  • Make sure all new .java files to have a simple Javadoc class comment with at least an +@author tag identifying you, and preferably at least a paragraph on what the class is +for.
  • Add the ASF license header comment to all new .java files (copy from existing files +in the project)
  • Add yourself as an @author to the .java files that you modify substantially (more +than cosmetic changes).
  • Add some Javadocs and, if you change the namespace, some XSD doc elements.
  • A few unit tests would help a lot as well — someone has to do it.
  • If no-one else is using your branch, please rebase it against the current master (or +other target branch in the main project).
  • When writing a commit message please follow these conventions, +if you are fixing an existing issue please add Fixes gh-XXXX at the end of the commit +message (where XXXX is the issue number).
\ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/spring-cloud-stream-binder-rabbit.html b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/spring-cloud-stream-binder-rabbit.html new file mode 100644 index 00000000..0fe010c9 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/spring-cloud-stream-binder-rabbit.html @@ -0,0 +1,117 @@ + + + + + + + +spring-cloud-stream-binder-rabbit + + + + + + + + +
+
+
+
+

2.1.4.RELEASE

+
+
+
+
+

Pick The Documentation Option

+
+
+ +
+
+
+
+ + + + + \ No newline at end of file diff --git a/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/spring-cloud-stream-binder-rabbit.xml b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/spring-cloud-stream-binder-rabbit.xml new file mode 100644 index 00000000..ad40fa86 --- /dev/null +++ b/spring-cloud-stream-binder-rabbit/2.1.4.RELEASE/spring-cloud-stream-binder-rabbit.xml @@ -0,0 +1,1497 @@ + + + + + +Spring Cloud Stream RabbitMQ Binder Reference Guide +2019-08-28 + + +Sabby Anandan, Marius Bogoevici, Eric Bottard, Mark Fisher, Ilayaperumal Gopinathan, Gunnar Hillert, Mark Pollack, Patrick Peralta, Glenn Renfro, Thomas Risberg, Dave Syer, David Turanski, Janne Valkealahti, Benjamin Klein, Gary Russell, Jay Bryant + + +S + + +Reference Guide + +This guide describes the RabbitMQ implementation of the Spring Cloud Stream Binder. +It contains information about its design, usage and configuration options, as well as information on how the Stream Cloud Stream concepts map into RabbitMQ specific constructs. + + +Usage +To use the RabbitMQ binder, you can add it to your Spring Cloud Stream application, by using the following Maven coordinates: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-stream-binder-rabbit</artifactId> +</dependency> +Alternatively, you can use the Spring Cloud Stream RabbitMQ Starter, as follows: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-stream-rabbit</artifactId> +</dependency> + + +RabbitMQ Binder Overview +The following simplified diagram shows how the RabbitMQ binder operates: +
+RabbitMQ Binder + + + + +rabbit binder + +
+By default, the RabbitMQ Binder implementation maps each destination to a TopicExchange. +For each consumer group, a Queue is bound to that TopicExchange. +Each consumer instance has a corresponding RabbitMQ Consumer instance for its group’s Queue. +For partitioned producers and consumers, the queues are suffixed with the partition index and use the partition index as the routing key. +For anonymous consumers (those with no group property), an auto-delete queue (with a randomized unique name) is used. +By using the optional autoBindDlq option, you can configure the binder to create and configure dead-letter queues (DLQs) (and a dead-letter exchange DLX, as well as routing infrastructure). +By default, the dead letter queue has the name of the destination, appended with .dlq. +If retry is enabled (maxAttempts > 1), failed messages are delivered to the DLQ after retries are exhausted. +If retry is disabled (maxAttempts = 1), you should set requeueRejected to false (the default) so that failed messages are routed to the DLQ, instead of being re-queued. +In addition, republishToDlq causes the binder to publish a failed message to the DLQ (instead of rejecting it). +This feature lets additional information (such as the stack trace in the x-exception-stacktrace header) be added to the message in headers. +See the frameMaxHeadroom property for information about truncated stack traces. +This option does not need retry enabled. +You can republish a failed message after just one attempt. +Starting with version 1.2, you can configure the delivery mode of republished messages. +See the republishDeliveryMode property. +If the stream listener throws an ImmediateAcknowledgeAmqpException, the DLQ is bypassed and the message simply discarded. +Starting with version 2.1, this is true regardless of the setting of republishToDlq; previously it was only the case when republishToDlq was false. + +Setting requeueRejected to true (with republishToDlq=false ) causes the message to be re-queued and redelivered continually, which is likely not what you want unless the reason for the failure is transient. +In general, you should enable retry within the binder by setting maxAttempts to greater than one or by setting republishToDlq to true. + +See for more information about these properties. +The framework does not provide any standard mechanism to consume dead-letter messages (or to re-route them back to the primary queue). +Some options are described in . + +When multiple RabbitMQ binders are used in a Spring Cloud Stream application, it is important to disable 'RabbitAutoConfiguration' to avoid the same configuration from RabbitAutoConfiguration being applied to the two binders. +You can exclude the class by using the @SpringBootApplication annotation. + +Starting with version 2.0, the RabbitMessageChannelBinder sets the RabbitTemplate.userPublisherConnection property to true so that the non-transactional producers avoid deadlocks on consumers, which can happen if cached connections are blocked because of a memory alarm on the broker. + +Currently, a multiplex consumer (a single consumer listening to multiple queues) is only supported for message-driven conssumers; polled consumers can only retrieve messages from a single queue. + +
+ +Configuration Options +This section contains settings specific to the RabbitMQ Binder and bound channels. +For general binding configuration options and properties, see the Spring Cloud Stream core documentation. +
+RabbitMQ Binder Properties +By default, the RabbitMQ binder uses Spring Boot’s ConnectionFactory. +Conseuqently, it supports all Spring Boot configuration options for RabbitMQ. +(For reference, see the Spring Boot documentation). +RabbitMQ configuration options use the spring.rabbitmq prefix. +In addition to Spring Boot options, the RabbitMQ binder supports the following properties: + + +spring.cloud.stream.rabbit.binder.adminAddresses + +A comma-separated list of RabbitMQ management plugin URLs. +Only used when nodes contains more than one entry. +Each entry in this list must have a corresponding entry in spring.rabbitmq.addresses. +Only needed if you use a RabbitMQ cluster and wish to consume from the node that hosts the queue. +See Queue Affinity and the LocalizedQueueConnectionFactory for more information. +Default: empty. + + + +spring.cloud.stream.rabbit.binder.nodes + +A comma-separated list of RabbitMQ node names. +When more than one entry, used to locate the server address where a queue is located. +Each entry in this list must have a corresponding entry in spring.rabbitmq.addresses. +Only needed if you use a RabbitMQ cluster and wish to consume from the node that hosts the queue. +See Queue Affinity and the LocalizedQueueConnectionFactory for more information. +Default: empty. + + + +spring.cloud.stream.rabbit.binder.compressionLevel + +The compression level for compressed bindings. +See java.util.zip.Deflater. +Default: 1 (BEST_LEVEL). + + + +spring.cloud.stream.binder.connection-name-prefix + +A connection name prefix used to name the connection(s) created by this binder. +The name is this prefix followed by #n, where n increments each time a new connection is opened. +Default: none (Spring AMQP default). + + + +
+
+RabbitMQ Consumer Properties +The following properties are available for Rabbit consumers only and must be prefixed with spring.cloud.stream.rabbit.bindings.<channelName>.consumer.. + + +acknowledgeMode + +The acknowledge mode. +Default: AUTO. + + + +anonymousGroupPrefix + +When the binding has no group property, an anonymous, auto-delete queue is bound to the destination exchange. +The default naming stragegy for such queues results in a queue named anonymous.<base64 representation of a UUID>. +Set this property to change the prefix to something other than the default. +Default: anonymous.. + + + +autoBindDlq + +Whether to automatically declare the DLQ and bind it to the binder DLX. +Default: false. + + + +bindingRoutingKey + +The routing key with which to bind the queue to the exchange (if bindQueue is true). +For partitioned destinations, -<instanceIndex> is appended. +Default: #. + + + +bindQueue + +Whether to declare the queue and bind it to the destination exchange. +Set it to false if you have set up your own infrastructure and have previously created and bound the queue. +Default: true. + + + +consumerTagPrefix + +Used to create the consumer tag(s); will be appended by #n where n increments for each consumer created. +Example: ${spring.application.name}-${spring.cloud.stream.bindings.input.group}-${spring.cloud.stream.instance-index}. +Default: none - the broker will generate random consumer tags. + + + +containerType + +Select the type of listener container to be used. +See Choosing a Container in the Spring AMQP documentation for more information. +Default: simple + + + +deadLetterQueueName + +The name of the DLQ +Default: prefix+destination.dlq + + + +deadLetterExchange + +A DLX to assign to the queue. +Relevant only if autoBindDlq is true. +Default: 'prefix+DLX' + + + +deadLetterExchangeType + +The type of the DLX to assign to the queue. +Relevant only if autoBindDlq is true. +Default: 'direct' + + + +deadLetterRoutingKey + +A dead letter routing key to assign to the queue. +Relevant only if autoBindDlq is true. +Default: destination + + + +declareDlx + +Whether to declare the dead letter exchange for the destination. +Relevant only if autoBindDlq is true. +Set to false if you have a pre-configured DLX. +Default: true. + + + +declareExchange + +Whether to declare the exchange for the destination. +Default: true. + + + +delayedExchange + +Whether to declare the exchange as a Delayed Message Exchange. +Requires the delayed message exchange plugin on the broker. +The x-delayed-type argument is set to the exchangeType. +Default: false. + + + +dlqDeadLetterExchange + +If a DLQ is declared, a DLX to assign to that queue. +Default: none + + + +dlqDeadLetterRoutingKey + +If a DLQ is declared, a dead letter routing key to assign to that queue. +Default: none + + + +dlqExpires + +How long before an unused dead letter queue is deleted (in milliseconds). +Default: no expiration + + + +dlqLazy + +Declare the dead letter queue with the x-queue-mode=lazy argument. +See Lazy Queues. +Consider using a policy instead of this setting, because using a policy allows changing the setting without deleting the queue. +Default: false. + + + +dlqMaxLength + +Maximum number of messages in the dead letter queue. +Default: no limit + + + +dlqMaxLengthBytes + +Maximum number of total bytes in the dead letter queue from all messages. +Default: no limit + + + +dlqMaxPriority + +Maximum priority of messages in the dead letter queue (0-255). +Default: none + + + +dlqOverflowBehavior + +Action to take when dlqMaxLength or dlqMaxLengthBytes is exceeded; currently drop-head or reject-publish but refer to the RabbitMQ documentation. +Default: none + + + +dlqTtl + +Default time to live to apply to the dead letter queue when declared (in milliseconds). +Default: no limit + + + +durableSubscription + +Whether the subscription should be durable. +Only effective if group is also set. +Default: true. + + + +exchangeAutoDelete + +If declareExchange is true, whether the exchange should be auto-deleted (that is, removed after the last queue is removed). +Default: true. + + + +exchangeDurable + +If declareExchange is true, whether the exchange should be durable (that is, it survives broker restart). +Default: true. + + + +exchangeType + +The exchange type: direct, fanout or topic for non-partitioned destinations and direct or topic for partitioned destinations. +Default: topic. + + + +exclusive + +Whether to create an exclusive consumer. +Concurrency should be 1 when this is true. +Often used when strict ordering is required but enabling a hot standby instance to take over after a failure. +See recoveryInterval, which controls how often a standby instance attempts to consume. +Default: false. + + + +expires + +How long before an unused queue is deleted (in milliseconds). +Default: no expiration + + + +failedDeclarationRetryInterval + +The interval (in milliseconds) between attempts to consume from a queue if it is missing. +Default: 5000 + + + + + +frameMaxHeadroom + +The number of bytes to reserve for other headers when adding the stack trace to a DLQ message header. +All headers must fit within the frame_max size configured on the broker. +Stack traces can be large; if the size plus this property exceeds frame_max then the stack trace will be truncated. +A WARN log will be written; consider increasing the frame_max or reducing the stack trace by catching the exception and throwing one with a smaller stack trace. +Default: 20000 + + + +headerPatterns + +Patterns for headers to be mapped from inbound messages. +Default: ['*'] (all headers). + + + +lazy + +Declare the queue with the x-queue-mode=lazy argument. +See Lazy Queues. +Consider using a policy instead of this setting, because using a policy allows changing the setting without deleting the queue. +Default: false. + + + +maxConcurrency + +The maximum number of consumers. +Not supported when the containerType is direct. +Default: 1. + + + +maxLength + +The maximum number of messages in the queue. +Default: no limit + + + +maxLengthBytes + +The maximum number of total bytes in the queue from all messages. +Default: no limit + + + +maxPriority + +The maximum priority of messages in the queue (0-255). +Default: none + + + +missingQueuesFatal + +When the queue cannot be found, whether to treat the condition as fatal and stop the listener container. +Defaults to false so that the container keeps trying to consume from the queue — for example, when using a cluster and the node hosting a non-HA queue is down. +Default: false + + + +overflowBehavior + +Action to take when maxLength or maxLengthBytes is exceeded; currently drop-head or reject-publish but refer to the RabbitMQ documentation. +Default: none + + + +prefetch + +Prefetch count. +Default: 1. + + + +prefix + +A prefix to be added to the name of the destination and queues. +Default: "". + + + +queueDeclarationRetries + +The number of times to retry consuming from a queue if it is missing. +Relevant only when missingQueuesFatal is true. +Otherwise, the container keeps retrying indefinitely. +Not supported when the containerType is direct. +Default: 3 + + + +queueNameGroupOnly + +When true, consume from a queue with a name equal to the group. +Otherwise the queue name is destination.group. +This is useful, for example, when using Spring Cloud Stream to consume from an existing RabbitMQ queue. +Default: false. + + + +recoveryInterval + +The interval between connection recovery attempts, in milliseconds. +Default: 5000. + + + +requeueRejected + +Whether delivery failures should be re-queued when retry is disabled or republishToDlq is false. +Default: false. + + + + + +republishDeliveryMode + +When republishToDlq is true, specifies the delivery mode of the republished message. +Default: DeliveryMode.PERSISTENT + + + +republishToDlq + +By default, messages that fail after retries are exhausted are rejected. +If a dead-letter queue (DLQ) is configured, RabbitMQ routes the failed message (unchanged) to the DLQ. +If set to true, the binder republishs failed messages to the DLQ with additional headers, including the exception message and stack trace from the cause of the final failure. +Also see the frameMaxHeadroom property. +Default: false + + + +transacted + +Whether to use transacted channels. +Default: false. + + + +ttl + +Default time to live to apply to the queue when declared (in milliseconds). +Default: no limit + + + +txSize + +The number of deliveries between acks. +Not supported when the containerType is direct. +Default: 1. + + + +
+
+Advanced Listener Container Configuration +To set listener container properties that are not exposed as binder or binding properties, add a single bean of type ListenerContainerCustomizer to the application context. +The binder and binding properties will be set and then the customizer will be called. +The customizer (configure() method) is provided with the queue name as well as the consumer group as arguments. +
+
+Rabbit Producer Properties +The following properties are available for Rabbit producers only and +must be prefixed with spring.cloud.stream.rabbit.bindings.<channelName>.producer.. + + +autoBindDlq + +Whether to automatically declare the DLQ and bind it to the binder DLX. +Default: false. + + + +batchingEnabled + +Whether to enable message batching by producers. +Messages are batched into one message according to the following properties (described in the next three entries in this list): 'batchSize', batchBufferLimit, and batchTimeout. +See Batching for more information. +Default: false. + + + +batchSize + +The number of messages to buffer when batching is enabled. +Default: 100. + + + +batchBufferLimit + +The maximum buffer size when batching is enabled. +Default: 10000. + + + +batchTimeout + +The batch timeout when batching is enabled. +Default: 5000. + + + +bindingRoutingKey + +The routing key with which to bind the queue to the exchange (if bindQueue is true). +Only applies to non-partitioned destinations. +Only applies if requiredGroups are provided and then only to those groups. +Default: #. + + + +bindQueue + +Whether to declare the queue and bind it to the destination exchange. +Set it to false if you have set up your own infrastructure and have previously created and bound the queue. +Only applies if requiredGroups are provided and then only to those groups. +Default: true. + + + +compress + +Whether data should be compressed when sent. +Default: false. + + + +confirmAckChannel + +When errorChannelEnabled is true, a channel to which to send positive delivery acknowledgments (aka publisher confirms). +If the channel does not exist, a DirectChannel is registered with this name. +The connection factory must be configured to enable publisher confirms. +Default: nullChannel (acks are discarded). + + + +deadLetterQueueName + +The name of the DLQ +Only applies if requiredGroups are provided and then only to those groups. +Default: prefix+destination.dlq + + + +deadLetterExchange + +A DLX to assign to the queue. +Relevant only when autoBindDlq is true. +Applies only when requiredGroups are provided and then only to those groups. +Default: 'prefix+DLX' + + + +deadLetterExchangeType + +The type of the DLX to assign to the queue. +Relevant only if autoBindDlq is true. +Applies only when requiredGroups are provided and then only to those groups. +Default: 'direct' + + + +deadLetterRoutingKey + +A dead letter routing key to assign to the queue. +Relevant only when autoBindDlq is true. +Applies only when requiredGroups are provided and then only to those groups. +Default: destination + + + +declareDlx + +Whether to declare the dead letter exchange for the destination. +Relevant only if autoBindDlq is true. +Set to false if you have a pre-configured DLX. +Applies only when requiredGroups are provided and then only to those groups. +Default: true. + + + +declareExchange + +Whether to declare the exchange for the destination. +Default: true. + + + +delayExpression + +A SpEL expression to evaluate the delay to apply to the message (x-delay header). +It has no effect if the exchange is not a delayed message exchange. +Default: No x-delay header is set. + + + +delayedExchange + +Whether to declare the exchange as a Delayed Message Exchange. +Requires the delayed message exchange plugin on the broker. +The x-delayed-type argument is set to the exchangeType. +Default: false. + + + +deliveryMode + +The delivery mode. +Default: PERSISTENT. + + + +dlqDeadLetterExchange + +When a DLQ is declared, a DLX to assign to that queue. +Applies only if requiredGroups are provided and then only to those groups. +Default: none + + + +dlqDeadLetterRoutingKey + +When a DLQ is declared, a dead letter routing key to assign to that queue. +Applies only when requiredGroups are provided and then only to those groups. +Default: none + + + +dlqExpires + +How long (in milliseconds) before an unused dead letter queue is deleted. +Applies only when requiredGroups are provided and then only to those groups. +Default: no expiration + + + +dlqLazy + +Declare the dead letter queue with the x-queue-mode=lazy argument. +See Lazy Queues. +Consider using a policy instead of this setting, because using a policy allows changing the setting without deleting the queue. +Applies only when requiredGroups are provided and then only to those groups. + + + +dlqMaxLength + +Maximum number of messages in the dead letter queue. +Applies only if requiredGroups are provided and then only to those groups. +Default: no limit + + + +dlqMaxLengthBytes + +Maximum number of total bytes in the dead letter queue from all messages. +Applies only when requiredGroups are provided and then only to those groups. +Default: no limit + + + +dlqMaxPriority + +Maximum priority of messages in the dead letter queue (0-255) +Applies only when requiredGroups are provided and then only to those groups. +Default: none + + + +dlqTtl + +Default time (in milliseconds) to live to apply to the dead letter queue when declared. +Applies only when requiredGroups are provided and then only to those groups. +Default: no limit + + + +exchangeAutoDelete + +If declareExchange is true, whether the exchange should be auto-delete (it is removed after the last queue is removed). +Default: true. + + + +exchangeDurable + +If declareExchange is true, whether the exchange should be durable (survives broker restart). +Default: true. + + + +exchangeType + +The exchange type: direct, fanout or topic for non-partitioned destinations and direct or topic for partitioned destinations. +Default: topic. + + + +expires + +How long (in milliseconds) before an unused queue is deleted. +Applies only when requiredGroups are provided and then only to those groups. +Default: no expiration + + + +headerPatterns + +Patterns for headers to be mapped to outbound messages. +Default: ['*'] (all headers). + + + +lazy + +Declare the queue with the x-queue-mode=lazy argument. +See Lazy Queues. +Consider using a policy instead of this setting, because using a policy allows changing the setting without deleting the queue. +Applies only when requiredGroups are provided and then only to those groups. +Default: false. + + + +maxLength + +Maximum number of messages in the queue. +Applies only when requiredGroups are provided and then only to those groups. +Default: no limit + + + +maxLengthBytes + +Maximum number of total bytes in the queue from all messages. +Only applies if requiredGroups are provided and then only to those groups. +Default: no limit + + + +maxPriority + +Maximum priority of messages in the queue (0-255). +Only applies if requiredGroups are provided and then only to those groups. +Default: none + + + +prefix + +A prefix to be added to the name of the destination exchange. +Default: "". + + + +queueNameGroupOnly + +When true, consume from a queue with a name equal to the group. +Otherwise the queue name is destination.group. +This is useful, for example, when using Spring Cloud Stream to consume from an existing RabbitMQ queue. +Applies only when requiredGroups are provided and then only to those groups. +Default: false. + + + +routingKeyExpression + +A SpEL expression to determine the routing key to use when publishing messages. +For a fixed routing key, use a literal expression, such as routingKeyExpression='my.routingKey' in a properties file or routingKeyExpression: '''my.routingKey''' in a YAML file. +Default: destination or destination-<partition> for partitioned destinations. + + + +transacted + +Whether to use transacted channels. +Default: false. + + + +ttl + +Default time (in milliseconds) to live to apply to the queue when declared. +Applies only when requiredGroups are provided and then only to those groups. +Default: no limit + + + + +In the case of RabbitMQ, content type headers can be set by external applications. +Spring Cloud Stream supports them as part of an extended internal protocol used for any type of transport — including transports, such as Kafka (prior to 0.11), that do not natively support headers. + +
+
+ +Retry With the RabbitMQ Binder +When retry is enabled within the binder, the listener container thread is suspended for any back off periods that are configured. +This might be important when strict ordering is required with a single consumer. However, for other use cases, it prevents other messages from being processed on that thread. +An alternative to using binder retry is to set up dead lettering with time to live on the dead-letter queue (DLQ) as well as dead-letter configuration on the DLQ itself. +See for more information about the properties discussed here. +You can use the following example configuration to enable this feature: + + +Set autoBindDlq to true. +The binder create a DLQ. +Optionally, you can specify a name in deadLetterQueueName. + + +Set dlqTtl to the back off time you want to wait between redeliveries. + + +Set the dlqDeadLetterExchange to the default exchange. +Expired messages from the DLQ are routed to the original queue, because the default deadLetterRoutingKey is the queue name (destination.group). +Setting to the default exchange is achieved by setting the property with no value, as shown in the next example. + + +To force a message to be dead-lettered, either throw an AmqpRejectAndDontRequeueException or set requeueRejected to true (the default) and throw any exception. +The loop continue without end, which is fine for transient problems, but you may want to give up after some number of attempts. +Fortunately, RabbitMQ provides the x-death header, which lets you determine how many cycles have occurred. +To acknowledge a message after giving up, throw an ImmediateAcknowledgeAmqpException. +
+Putting it All Together +The following configuration creates an exchange myDestination with queue myDestination.consumerGroup bound to a topic exchange with a wildcard routing key #: +--- +spring.cloud.stream.bindings.input.destination=myDestination +spring.cloud.stream.bindings.input.group=consumerGroup +#disable binder retries +spring.cloud.stream.bindings.input.consumer.max-attempts=1 +#dlx/dlq setup +spring.cloud.stream.rabbit.bindings.input.consumer.auto-bind-dlq=true +spring.cloud.stream.rabbit.bindings.input.consumer.dlq-ttl=5000 +spring.cloud.stream.rabbit.bindings.input.consumer.dlq-dead-letter-exchange= +--- +This configuration creates a DLQ bound to a direct exchange (DLX) with a routing key of myDestination.consumerGroup. +When messages are rejected, they are routed to the DLQ. +After 5 seconds, the message expires and is routed to the original queue by using the queue name as the routing key, as shown in the following example: + +Spring Boot application + +@SpringBootApplication +@EnableBinding(Sink.class) +public class XDeathApplication { + + public static void main(String[] args) { + SpringApplication.run(XDeathApplication.class, args); + } + + @StreamListener(Sink.INPUT) + public void listen(String in, @Header(name = "x-death", required = false) Map<?,?> death) { + if (death != null && death.get("count").equals(3L)) { + // giving up - don't send to DLX + throw new ImmediateAcknowledgeAmqpException("Failed after 4 attempts"); + } + throw new AmqpRejectAndDontRequeueException("failed"); + } + +} + + +Notice that the count property in the x-death header is a Long. +
+
+ +Error Channels +Starting with version 1.3, the binder unconditionally sends exceptions to an error channel for each consumer destination and can also be configured to send async producer send failures to an error channel. +See for more information. +RabbitMQ has two types of send failures: + + +Returned messages, + + +Negatively acknowledged Publisher Confirms. + + +The latter is rare. +According to the RabbitMQ documentation "[A nack] will only be delivered if an internal error occurs in the Erlang process responsible for a queue.". +As well as enabling producer error channels (as described in ), the RabbitMQ binder only sends messages to the channels if the connection factory is appropriately configured, as follows: + + +ccf.setPublisherConfirms(true); + + +ccf.setPublisherReturns(true); + + +When using Spring Boot configuration for the connection factory, set the following properties: + + +spring.rabbitmq.publisher-confirms + + +spring.rabbitmq.publisher-returns + + +The payload of the ErrorMessage for a returned message is a ReturnedAmqpMessageException with the following properties: + + +failedMessage: The spring-messaging Message<?> that failed to be sent. + + +amqpMessage: The raw spring-amqp Message. + + +replyCode: An integer value indicating the reason for the failure (for example, 312 - No route). + + +replyText: A text value indicating the reason for the failure (for example, NO_ROUTE). + + +exchange: The exchange to which the message was published. + + +routingKey: The routing key used when the message was published. + + +For negatively acknowledged confirmations, the payload is a NackedAmqpMessageException with the following properties: + + +failedMessage: The spring-messaging Message<?> that failed to be sent. + + +nackReason: A reason (if available — you may need to examine the broker logs for more information). + + +There is no automatic handling of these exceptions (such as sending to a dead-letter queue). +You can consume these exceptions with your own Spring Integration flow. + + +Dead-Letter Queue Processing +Because you cannot anticipate how users would want to dispose of dead-lettered messages, the framework does not provide any standard mechanism to handle them. +If the reason for the dead-lettering is transient, you may wish to route the messages back to the original queue. +However, if the problem is a permanent issue, that could cause an infinite loop. +The following Spring Boot application shows an example of how to route those messages back to the original queue but moves them to a third parking lot queue after three attempts. +The second example uses the RabbitMQ Delayed Message Exchange to introduce a delay to the re-queued message. +In this example, the delay increases for each attempt. +These examples use a @RabbitListener to receive messages from the DLQ. +You could also use RabbitTemplate.receive() in a batch process. +The examples assume the original destination is so8400in and the consumer group is so8400. +
+Non-Partitioned Destinations +The first two examples are for when the destination is not partitioned: +@SpringBootApplication +public class ReRouteDlqApplication { + + private static final String ORIGINAL_QUEUE = "so8400in.so8400"; + + private static final String DLQ = ORIGINAL_QUEUE + ".dlq"; + + private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot"; + + private static final String X_RETRIES_HEADER = "x-retries"; + + public static void main(String[] args) throws Exception { + ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args); + System.out.println("Hit enter to terminate"); + System.in.read(); + context.close(); + } + + @Autowired + private RabbitTemplate rabbitTemplate; + + @RabbitListener(queues = DLQ) + public void rePublish(Message failedMessage) { + Integer retriesHeader = (Integer) failedMessage.getMessageProperties().getHeaders().get(X_RETRIES_HEADER); + if (retriesHeader == null) { + retriesHeader = Integer.valueOf(0); + } + if (retriesHeader < 3) { + failedMessage.getMessageProperties().getHeaders().put(X_RETRIES_HEADER, retriesHeader + 1); + this.rabbitTemplate.send(ORIGINAL_QUEUE, failedMessage); + } + else { + this.rabbitTemplate.send(PARKING_LOT, failedMessage); + } + } + + @Bean + public Queue parkingLot() { + return new Queue(PARKING_LOT); + } + +} +@SpringBootApplication +public class ReRouteDlqApplication { + + private static final String ORIGINAL_QUEUE = "so8400in.so8400"; + + private static final String DLQ = ORIGINAL_QUEUE + ".dlq"; + + private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot"; + + private static final String X_RETRIES_HEADER = "x-retries"; + + private static final String DELAY_EXCHANGE = "dlqReRouter"; + + public static void main(String[] args) throws Exception { + ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args); + System.out.println("Hit enter to terminate"); + System.in.read(); + context.close(); + } + + @Autowired + private RabbitTemplate rabbitTemplate; + + @RabbitListener(queues = DLQ) + public void rePublish(Message failedMessage) { + Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders(); + Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER); + if (retriesHeader == null) { + retriesHeader = Integer.valueOf(0); + } + if (retriesHeader < 3) { + headers.put(X_RETRIES_HEADER, retriesHeader + 1); + headers.put("x-delay", 5000 * retriesHeader); + this.rabbitTemplate.send(DELAY_EXCHANGE, ORIGINAL_QUEUE, failedMessage); + } + else { + this.rabbitTemplate.send(PARKING_LOT, failedMessage); + } + } + + @Bean + public DirectExchange delayExchange() { + DirectExchange exchange = new DirectExchange(DELAY_EXCHANGE); + exchange.setDelayed(true); + return exchange; + } + + @Bean + public Binding bindOriginalToDelay() { + return BindingBuilder.bind(new Queue(ORIGINAL_QUEUE)).to(delayExchange()).with(ORIGINAL_QUEUE); + } + + @Bean + public Queue parkingLot() { + return new Queue(PARKING_LOT); + } + +} +
+
+Partitioned Destinations +With partitioned destinations, there is one DLQ for all partitions. We determine the original queue from the headers. +
+<literal>republishToDlq=false</literal> +When republishToDlq is false, RabbitMQ publishes the message to the DLX/DLQ with an x-death header containing information about the original destination, as shown in the following example: +@SpringBootApplication +public class ReRouteDlqApplication { + + private static final String ORIGINAL_QUEUE = "so8400in.so8400"; + + private static final String DLQ = ORIGINAL_QUEUE + ".dlq"; + + private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot"; + + private static final String X_DEATH_HEADER = "x-death"; + + private static final String X_RETRIES_HEADER = "x-retries"; + + public static void main(String[] args) throws Exception { + ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args); + System.out.println("Hit enter to terminate"); + System.in.read(); + context.close(); + } + + @Autowired + private RabbitTemplate rabbitTemplate; + + @SuppressWarnings("unchecked") + @RabbitListener(queues = DLQ) + public void rePublish(Message failedMessage) { + Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders(); + Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER); + if (retriesHeader == null) { + retriesHeader = Integer.valueOf(0); + } + if (retriesHeader < 3) { + headers.put(X_RETRIES_HEADER, retriesHeader + 1); + List<Map<String, ?>> xDeath = (List<Map<String, ?>>) headers.get(X_DEATH_HEADER); + String exchange = (String) xDeath.get(0).get("exchange"); + List<String> routingKeys = (List<String>) xDeath.get(0).get("routing-keys"); + this.rabbitTemplate.send(exchange, routingKeys.get(0), failedMessage); + } + else { + this.rabbitTemplate.send(PARKING_LOT, failedMessage); + } + } + + @Bean + public Queue parkingLot() { + return new Queue(PARKING_LOT); + } + +} +
+
+<literal>republishToDlq=true</literal> +When republishToDlq is true, the republishing recoverer adds the original exchange and routing key to headers, as shown in the following example: +@SpringBootApplication +public class ReRouteDlqApplication { + + private static final String ORIGINAL_QUEUE = "so8400in.so8400"; + + private static final String DLQ = ORIGINAL_QUEUE + ".dlq"; + + private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot"; + + private static final String X_RETRIES_HEADER = "x-retries"; + + private static final String X_ORIGINAL_EXCHANGE_HEADER = RepublishMessageRecoverer.X_ORIGINAL_EXCHANGE; + + private static final String X_ORIGINAL_ROUTING_KEY_HEADER = RepublishMessageRecoverer.X_ORIGINAL_ROUTING_KEY; + + public static void main(String[] args) throws Exception { + ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args); + System.out.println("Hit enter to terminate"); + System.in.read(); + context.close(); + } + + @Autowired + private RabbitTemplate rabbitTemplate; + + @RabbitListener(queues = DLQ) + public void rePublish(Message failedMessage) { + Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders(); + Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER); + if (retriesHeader == null) { + retriesHeader = Integer.valueOf(0); + } + if (retriesHeader < 3) { + headers.put(X_RETRIES_HEADER, retriesHeader + 1); + String exchange = (String) headers.get(X_ORIGINAL_EXCHANGE_HEADER); + String originalRoutingKey = (String) headers.get(X_ORIGINAL_ROUTING_KEY_HEADER); + this.rabbitTemplate.send(exchange, originalRoutingKey, failedMessage); + } + else { + this.rabbitTemplate.send(PARKING_LOT, failedMessage); + } + } + + @Bean + public Queue parkingLot() { + return new Queue(PARKING_LOT); + } + +} +
+
+
+ +Partitioning with the RabbitMQ Binder +RabbitMQ does not support partitioning natively. +Sometimes, it is advantageous to send data to specific partitions — for example, when you want to strictly order message processing, all messages for a particular customer should go to the same partition. +The RabbitMessageChannelBinder provides partitioning by binding a queue for each partition to the destination exchange. +The following Java and YAML examples show how to configure the producer: + +Producer + +@SpringBootApplication +@EnableBinding(Source.class) +public class RabbitPartitionProducerApplication { + + private static final Random RANDOM = new Random(System.currentTimeMillis()); + + private static final String[] data = new String[] { + "abc1", "def1", "qux1", + "abc2", "def2", "qux2", + "abc3", "def3", "qux3", + "abc4", "def4", "qux4", + }; + + public static void main(String[] args) { + new SpringApplicationBuilder(RabbitPartitionProducerApplication.class) + .web(false) + .run(args); + } + + @InboundChannelAdapter(channel = Source.OUTPUT, poller = @Poller(fixedRate = "5000")) + public Message<?> generate() { + String value = data[RANDOM.nextInt(data.length)]; + System.out.println("Sending: " + value); + return MessageBuilder.withPayload(value) + .setHeader("partitionKey", value) + .build(); + } + +} + + + +application.yml + + spring: + cloud: + stream: + bindings: + output: + destination: partitioned.destination + producer: + partitioned: true + partition-key-expression: headers['partitionKey'] + partition-count: 2 + required-groups: + - myGroup + + + +The configuration in the prececing example uses the default partitioning (key.hashCode() % partitionCount). +This may or may not provide a suitably balanced algorithm, depending on the key values. +You can override this default by using the partitionSelectorExpression or partitionSelectorClass properties. +The required-groups property is required only if you need the consumer queues to be provisioned when the producer is deployed. +Otherwise, any messages sent to a partition are lost until the corresponding consumer is deployed. + +The following configuration provisions a topic exchange: + + + + + +part exchange + + +The following queues are bound to that exchange: + + + + + +part queues + + +The following bindings associate the queues to the exchange: + + + + + +part bindings + + +The following Java and YAML examples continue the previous examples and show how to configure the consumer: + +Consumer + +@SpringBootApplication +@EnableBinding(Sink.class) +public class RabbitPartitionConsumerApplication { + + public static void main(String[] args) { + new SpringApplicationBuilder(RabbitPartitionConsumerApplication.class) + .web(false) + .run(args); + } + + @StreamListener(Sink.INPUT) + public void listen(@Payload String in, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue) { + System.out.println(in + " received from queue " + queue); + } + +} + + + +application.yml + + spring: + cloud: + stream: + bindings: + input: + destination: partitioned.destination + group: myGroup + consumer: + partitioned: true + instance-index: 0 + + + +The RabbitMessageChannelBinder does not support dynamic scaling. +There must be at least one consumer per partition. +The consumer’s instanceIndex is used to indicate which partition is consumed. +Platforms such as Cloud Foundry can have only one instance with an instanceIndex. + + +
+ +Appendices + +Building +
+Basic Compile and Test +To build the source you will need to install JDK 1.8. +The build uses the Maven wrapper so you don’t have to install a specific +version of Maven. To enable the tests, you should have RabbitMQ server running +on localhost and the default port (5672) +before building. +The main build command is +$ ./mvnw clean install +You can also add '-DskipTests' if you like, to avoid running the tests. + +You can also install Maven (>=3.3.3) yourself and run the mvn command +in place of ./mvnw in the examples below. If you do that you also +might need to add -P spring if your local Maven settings do not +contain repository declarations for spring pre-release artifacts. + + +Be aware that you might need to increase the amount of memory +available to Maven by setting a MAVEN_OPTS environment variable with +a value like -Xmx512m -XX:MaxPermSize=128m. We try to cover this in +the .mvn configuration, so if you find you have to do it to make a +build succeed, please raise a ticket to get the settings added to +source control. + +The projects that require middleware generally include a +docker-compose.yml, so consider using +Docker Compose to run the middeware servers +in Docker containers. +
+
+Documentation +There is a "full" profile that will generate documentation. +
+
+Working with the code +If you don’t have an IDE preference we would recommend that you use +Spring Tools Suite or +Eclipse when working with the code. We use the +m2eclipe eclipse plugin for maven support. Other IDEs and tools +should also work without issue. +
+Importing into eclipse with m2eclipse +We recommend the m2eclipe eclipse plugin when working with +eclipse. If you don’t already have m2eclipse installed it is available from the "eclipse +marketplace". +Unfortunately m2e does not yet support Maven 3.3, so once the projects +are imported into Eclipse you will also need to tell m2eclipse to use +the .settings.xml file for the projects. If you do not do this you +may see many different errors related to the POMs in the +projects. Open your Eclipse preferences, expand the Maven +preferences, and select User Settings. In the User Settings field +click Browse and navigate to the Spring Cloud project you imported +selecting the .settings.xml file in that project. Click Apply and +then OK to save the preference changes. + +Alternatively you can copy the repository settings from .settings.xml into your own ~/.m2/settings.xml. + +
+
+Importing into eclipse without m2eclipse +If you prefer not to use m2eclipse you can generate eclipse project metadata using the +following command: +$ ./mvnw eclipse:eclipse +The generated eclipse projects can be imported by selecting import existing projects +from the file menu. +
+
+
+ +Contributing +Spring Cloud is released under the non-restrictive Apache 2.0 license, +and follows a very standard Github development process, using Github +tracker for issues and merging pull requests into master. If you want +to contribute even something trivial please do not hesitate, but +follow the guidelines below. +
+Sign the Contributor License Agreement +Before we accept a non-trivial patch or pull request we will need you to sign the +contributor’s agreement. +Signing the contributor’s agreement does not grant anyone commit rights to the main +repository, but it does mean that we can accept your contributions, and you will get an +author credit if we do. Active contributors might be asked to join the core team, and +given the ability to merge pull requests. +
+
+Code Conventions and Housekeeping +None of these is essential for a pull request, but they will all help. They can also be +added after the original pull request but before a merge. + + +Use the Spring Framework code format conventions. If you use Eclipse +you can import formatter settings using the +eclipse-code-formatter.xml file from the +Spring +Cloud Build project. If using IntelliJ, you can use the +Eclipse Code Formatter +Plugin to import the same file. + + +Make sure all new .java files to have a simple Javadoc class comment with at least an +@author tag identifying you, and preferably at least a paragraph on what the class is +for. + + +Add the ASF license header comment to all new .java files (copy from existing files +in the project) + + +Add yourself as an @author to the .java files that you modify substantially (more +than cosmetic changes). + + +Add some Javadocs and, if you change the namespace, some XSD doc elements. + + +A few unit tests would help a lot as well — someone has to do it. + + +If no-one else is using your branch, please rebase it against the current master (or +other target branch in the main project). + + +When writing a commit message please follow these conventions, +if you are fixing an existing issue please add Fixes gh-XXXX at the end of the commit +message (where XXXX is the issue number). + + +
+
+
+
\ No newline at end of file