<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Talk is cheap,show me the code</title>
  
  <subtitle>开源，协作，共享，进步</subtitle>
  <link href="https://nydia.github.io/atom.xml" rel="self"/>
  
  <link href="https://nydia.github.io/"/>
  <updated>2025-04-26T11:27:26.855Z</updated>
  <id>https://nydia.github.io/</id>
  
  <author>
    <name>nydia</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Flutter</title>
    <link href="https://nydia.github.io/2022/10/01/flutter/flutter/"/>
    <id>https://nydia.github.io/2022/10/01/flutter/flutter/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T11:27:26.855Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Flutter简介"><a href="#Flutter简介" class="headerlink" title="Flutter简介"></a>Flutter简介</h2><h3 id="极速构建漂亮的原生应用"><a href="#极速构建漂亮的原生应用" class="headerlink" title="极速构建漂亮的原生应用"></a>极速构建漂亮的原生应用</h3><p>Flutter是谷歌的移动UI框架，可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界，Flutter正在被越来越多的开发者和组织使用，并且Flutter是完全免费、开源的。</p><h2 id="Flutter的安装"><a href="#Flutter的安装" class="headerlink" title="Flutter的安装"></a>Flutter的安装</h2><ol><li><p>安装Git工具，因为获去Flutter需要Git命令</p></li><li><p>获取Flutter : git clone -b beta <a href="https://github.com/flutter/flutter.git">https://github.com/flutter/flutter.git</a></p></li><li><p>配置环境变量<br>a. 临时变量：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">export PUB_HOSTED_URL=https://pub.flutter-io.cn //国内用户需要设置</span><br><span class="line">export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn //国内用户需要置</span><br><span class="line">export PATH=`pwd`/flutter/bin:$PATH</span><br></pre></td></tr></table></figure><p>b. 永久变量：<br>转到 “控制面板&gt;用户帐户&gt;用户帐户&gt;更改我的环境变量”<br>在“用户变量”下检查是否有名为“Path”的条目:<br>如果该条目存在, 追加 flutter\bin的全路径，使用 ; 作为分隔符.<br>如果条目不存在, 创建一个新用户变量 Path ，然后将 flutter\bin的全路径作为它的值.<br>在“用户变量”下检查是否有名为”PUB_HOSTED_URL”和”FLUTTER_STORAGE_BASE_URL”的条目，如果没有，也添加它们。</p></li><li><p>安装依赖<br>运行 flutter doctor  检测需要的安装项</p></li><li><p>检测设备连接<br>flutter devices</p></li><li><p>Flutter开发工具IDEA开发环境搭建<br>安装flutter和dart插件，插件的安装安装下图操作：<br>file-&gt;setting-&gt;Browse repositories<br>或者选择install plugin from disk 本地安装</p></li><li><p>Flutter的android开发的环境搭建<br>a. 安装android sdk<br>b. idea里面配置jdk和android sdk<br>需要配置JDK和Android SDK</p></li></ol><h2 id="参考网站"><a href="#参考网站" class="headerlink" title="参考网站"></a>参考网站</h2><ol><li>Flutter百度百科： <a href="https://baike.baidu.com/item/Flutter/22498985">https://baike.baidu.com/item/Flutter/22498985</a></li><li>Flutter中文学习网址: <a href="https://flutterchina.club/">https://flutterchina.club</a></li><li>React Native 中文学习网： <a href="https://reactnative.cn/docs/0.20/getting-started.html">https://reactnative.cn/docs/0.20/getting-started.html</a></li><li>几款移动跨平台App开发框架比较： <a href="https://wiki.lhqmm.com/my/?wid=74b1c7589c684cf3abbbe86197f5e5fb">https://wiki.lhqmm.com/my/?wid=74b1c7589c684cf3abbbe86197f5e5fb</a></li><li>React Native百度百科：<a href="https://baike.baidu.com/item/react%20native/20307162?fr=aladdin">https://baike.baidu.com/item/react%20native/20307162?fr=aladdin</a></li><li>Dart百度百科：<a href="https://baike.baidu.com/item/dart%E8%AF%AD%E8%A8%80/4117161?fr=aladdin">https://baike.baidu.com/item/dart%E8%AF%AD%E8%A8%80/4117161?fr=aladdin</a></li><li>Dart学习社区： <a href="http://www.cndartlang.com/">http://www.cndartlang.com/</a></li></ol><h3 id="Android-studio下载地址"><a href="#Android-studio下载地址" class="headerlink" title="Android studio下载地址"></a>Android studio下载地址</h3><p><a href="https://developer.android.google.cn/">https://developer.android.google.cn/</a><br>原来的android开发者社区<a href="https://developer.android.google.com/">https://developer.android.google.com</a> 已经不能访问了。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Flutter简介&quot;&gt;&lt;a href=&quot;#Flutter简介&quot; class=&quot;headerlink&quot; title=&quot;Flutter简介&quot;&gt;&lt;/a&gt;Flutter简介&lt;/h2&gt;&lt;h3 id=&quot;极速构建漂亮的原生应用&quot;&gt;&lt;a href=&quot;#极速构建漂亮的原生应用&quot; c</summary>
      
    
    
    
    <category term="Flutter" scheme="https://nydia.github.io/categories/Flutter/"/>
    
    
    <category term="Flutter" scheme="https://nydia.github.io/tags/Flutter/"/>
    
  </entry>
  
  <entry>
    <title>技术工具</title>
    <link href="https://nydia.github.io/2022/10/01/tech-tools/README/"/>
    <id>https://nydia.github.io/2022/10/01/tech-tools/README/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T13:32:05.478Z</updated>
    
    <content type="html"><![CDATA[<h1 id="技术工具"><a href="#技术工具" class="headerlink" title="技术工具"></a>技术工具</h1><ol><li><a href="http://sfz.uzuzuz.com/">身份证生成器</a></li><li><a href="https://docs.microsoft.com/zh-cn/">windows系统官方使用文档</a></li></ol><h2 id="技术网址"><a href="#技术网址" class="headerlink" title="技术网址"></a>技术网址</h2><ul><li><a href="https://www.deepl.com/translator">https://www.deepl.com/translator 专业技术翻译网站</a></li><li><a href="https://www.websequencediagrams.com/">https://www.websequencediagrams.com 在线序列图</a></li><li><a href="http://bestcbooks.com/">http://bestcbooks.com/</a> 计算机书籍控</li><li><a href="https://leetcode-cn.com/">https://leetcode-cn.com/</a> 力扣（算法）</li><li><a href="https://dev.tencent.com/">https://dev.tencent.com</a> 腾讯云开发</li><li><a href="https://coding.net/">https://coding.net/</a> 码市</li><li><a href="https://stackoverflow.com/questions">https://stackoverflow.com/questions</a> 问题搜索博客</li><li><a href="https://blog.csdn.net/zhang89xiao/article/details/79084481">https://blog.csdn.net/zhang89xiao/article/details/79084481</a> 2017 开源中国评比的前100个优秀开源项目</li><li><a href="http://debugx5.qq.com/">http://debugx5.qq.com</a> 清除Android的微信&amp;公众号缓存</li><li><a href="https://gitbook.cn/">https://gitbook.cn</a> gitbook一个基于微信的只是分享</li><li><a href="https://www.eolinker.com/">https://www.eolinker.com</a> API管理</li><li><a href="https://developer.android.google.cn/">https://developer.android.google.cn/</a>  android开发者社区</li><li><a href="http://www.logosc.cn/">http://www.logosc.cn/</a> 在线logo生成</li><li><a href="https://pandao.github.io/editor.md/">https://pandao.github.io/editor.md/</a> 开源的markdown编辑器</li><li><a href="http://itchat.readthedocs.io/zh/latest/">http://itchat.readthedocs.io/zh/latest/</a> itchat Pyton微信接口</li><li><a href="https://segmentfault.com/a/1190000009420701">https://segmentfault.com/a/1190000009420701</a></li><li><a href="https://code.ziqiangxuetang.com/django/django-tutorial.html">https://code.ziqiangxuetang.com/django/django-tutorial.html</a> 自强学堂Django学习</li><li><a href="http://mustache.github.io/">http://mustache.github.io/</a> mustache官网</li><li><a href="https://github.com/janl/mustache.js">https://github.com/janl/mustache.js</a>  js里面运用mustache</li><li><a href="http://www.sucaihuo.com/">http://www.sucaihuo.com/</a> 素材火官网（素材网站）</li><li><a href="https://docs.docker.com/">https://docs.docker.com</a> docker 官网</li><li><a href="https://www.rfc-editor.org/errata_search.php?rfc=5789">https://www.rfc-editor.org/errata_search.php?rfc=5789</a> RFC网站</li><li><a href="https://pay.weixin.qq.com/wiki/doc/api/index.html">https://pay.weixin.qq.com/wiki/doc/api/index.html</a> 普通商户微信支付</li><li><a href="http://qydev.weixin.qq.com/wiki/index.php">http://qydev.weixin.qq.com/wiki/index.php</a> 企业号微信支付</li><li><a href="https://caniuse.com/">https://caniuse.com</a> 查看css的兼容性（不是太准确）</li><li><a href="http://nipponcolors.com/#sumire">http://nipponcolors.com/#sumire</a> 日本的一个视觉网站</li><li><a href="http://swlabs.co/madmin/code/ui-treeview.html">http://swlabs.co/madmin/code/ui-treeview.html</a> bootstrap3模板</li><li><a href="https://deuhd.ru/">https://deuhd.ru/</a>  俄罗斯出品的解锁加密影片   DeUHD</li><li><a href="https://pay.weixin.qq.com/wiki/doc/api/index.html">https://pay.weixin.qq.com/wiki/doc/api/index.html</a> 微信支付开发文档</li><li><a href="https://developer.android.com/">https://developer.android.com</a> android官网，需用VPN工具（lantern）才能打开</li><li><a href="https://github.com/otale/tale/wiki">https://github.com/otale/tale/wiki</a> tale使用</li><li><a href="https://supermind.nl/submin/current">https://supermind.nl/submin/current</a>  subversion web管理submin  下载地址</li><li><a href="https://github.com/jemalloc/jemalloc/releases/">https://github.com/jemalloc/jemalloc/releases/</a>  jemalloc 下载地址</li><li><a href="http://www.cssmoban.com/tags.asp?n=Bootstrap">http://www.cssmoban.com/tags.asp?n=Bootstrap</a>  模板之家，静态网站</li><li><a href="https://www.gitlab.com.cn/installation/#centos-7">https://www.gitlab.com.cn/installation/#centos-7</a>  gitlab 安装官方文档</li><li><a href="https://www.getlantern.org/en_US/">https://www.getlantern.org/en_US/</a> 蓝灯官网  vpn软件</li><li><a href="https://h5.baidu.com/">https://h5.baidu.com</a>  百度的H5编辑</li><li>支持一键导入 PSD、简单上传操作、在线编辑，全自动完成从 PSD 到 H5 页面雏型的转换</li><li><a href="http://cli.im/">http://cli.im/</a> 草料二维码生成</li><li><a href="http://www.lanrenzhijia.com/">http://www.lanrenzhijia.com/</a> 懒人之家   js&#x2F;css素材</li><li><a href="https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html?t=201715%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%B8%8B%E8%BD%BD">https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html?t=201715微信小程序下载</a></li><li><a href="http://www.rabbitmq.com/releases/erlang/">http://www.rabbitmq.com/releases/erlang/</a></li><li><a href="https://github.com/nuysoft/Mock/wiki/Getting-Started">https://github.com/nuysoft/Mock/wiki/Getting-Started</a> mock.js</li><li><a href="https://mp.weixin.qq.com/wiki?t=resource/res_main&amp;id=mp1445241432">https://mp.weixin.qq.com/wiki?t=resource/res_main&amp;id=mp1445241432</a> 微信公众号开发</li><li><a href="mailto:&#x31;&#48;&#50;&#56;&#x37;&#56;&#55;&#x37;&#54;&#x30;&#x40;&#x71;&#113;&#x2e;&#x63;&#x6f;&#109;">&#x31;&#48;&#50;&#56;&#x37;&#56;&#55;&#x37;&#54;&#x30;&#x40;&#x71;&#113;&#x2e;&#x63;&#x6f;&#109;</a>&#x2F;1988090591hua</li><li><a href="http://www.xmlpull.org/">http://www.xmlpull.org/</a> xml解析</li><li><a href="http://mvnrepository.com/artifact">http://mvnrepository.com/artifact</a> jar maven地址</li><li><a href="http://lbs.amap.com/api/javascript-api/example/district-search/city-drop-down-list">http://lbs.amap.com/api/javascript-api/example/district-search/city-drop-down-list</a>  高德地图接口</li><li><a href="http://developer.jasig.org/cas/">http://developer.jasig.org/cas/</a> cas下载</li><li><a href="http://jingyan.baidu.com/article/9989c746064d46f648ecfe9a.html">http://jingyan.baidu.com/article/9989c746064d46f648ecfe9a.html</a> 如何在oracle下载jdk历史版本</li><li><a href="https://sourceforge.net/projects/artifactory/">https://sourceforge.net/projects/artifactory/</a>  artifactory maven仓库构建 （用Nexus也行）</li><li><a href="https://mp.weixin.qq.com/wiki/home/">https://mp.weixin.qq.com/wiki/home/</a>  微信公众号开发文档</li><li><a href="http://www.ui.cn/">http://www.ui.cn/</a>  中国UI</li><li><a href="http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7-521261.html">http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7-521261.html</a> java</li><li><a href="http://www.17sucai.com/">http://www.17sucai.com/</a> 静态网站源码</li><li><a href="https://www.getpostman.com/apps">https://www.getpostman.com/apps</a> 接口调试软件postman网站</li><li><a href="http://www.dedecms.com/">http://www.dedecms.com/</a>  织梦静态网站模版</li><li><a href="http://help.dedecms.com/v53/">http://help.dedecms.com/v53/</a></li><li><a href="https://notepad-plus-plus.org/download/v7.3.html">https://notepad-plus-plus.org/download/v7.3.html</a> notepad++编辑器</li><li><a href="https://github.com/thx/RAP.wiki.git">https://github.com/thx/RAP.wiki.git</a> 可视化接口管理工具 RAP（淘宝开发）</li><li><a href="http://blog.csdn.net/fancylovejava/article/details/45787729/">http://blog.csdn.net/fancylovejava/article/details/45787729/</a> android的UI（炫酷的）</li><li><a href="https://www.pendrivelinux.com/universal-usb-installer-easy-as-1-2-3/">https://www.pendrivelinux.com/universal-usb-installer-easy-as-1-2-3/</a>  universal-usb-installer制作U盘装linux</li><li><a href="http://jingyan.baidu.com/article/fec4bce20e344cf2618d8b37.html">http://jingyan.baidu.com/article/fec4bce20e344cf2618d8b37.html</a></li><li><a href="http://www.cnblogs.com/VVingerfly/p/5217723.html">http://www.cnblogs.com/VVingerfly/p/5217723.html</a></li><li><a href="http://python.usyiyi.cn/django/intro/install.html">http://python.usyiyi.cn/django/intro/install.html</a> django文档</li><li><a href="http://materializecss.com/">http://materializecss.com/</a>   是基于谷歌Material Design Language(MDL)设计语言所构建的一套CSS&#x2F;HTML库</li><li><a href="https://github.com/Dogfalo/materialize">https://github.com/Dogfalo/materialize</a>  materialize源码</li><li><a href="http://materializecss.com/showcase.html">http://materializecss.com/showcase.html</a></li><li><a href="https://xituqu.com/640.html">https://xituqu.com/640.html</a>  稀土区， 静态资源。</li><li><a href="http://www.qrcode.com/en/">http://www.qrcode.com/en/</a> 二维码官网</li><li><a href="http://wenku.baidu.com/link?url=iq1lNyYmNdxXjr8mJHVLAwcLuuU_v4_AaZyY9AE9VqhvZnLRQuk6Dda1ILKoGrL1ZqWgd0ArqK1NzDFDn9Q2r3P1jOpKyxuSLlk4uDwGZv7">http://wenku.baidu.com/link?url=iq1lNyYmNdxXjr8mJHVLAwcLuuU_v4_AaZyY9AE9VqhvZnLRQuk6Dda1ILKoGrL1ZqWgd0ArqK1NzDFDn9Q2r3P1jOpKyxuSLlk4uDwGZv7</a></li><li><a href="http://www.myexception.cn/other/1847324.html">http://www.myexception.cn/other/1847324.html</a>   海蜘蛛软路由</li><li><a href="http://www.cygwin.com/">http://www.cygwin.com/</a> cygwin是windows平台上的posix系统模拟环境</li><li><a href="http://redisdoc.com/">http://redisdoc.com/</a>  redis命令</li><li><a href="https://redisdesktop.com/download">https://redisdesktop.com/download</a> redis可视化管理工具</li><li><a href="http://www.w3school.com.cn/">http://www.w3school.com.cn/</a>  w3school教程</li><li><a href="http://summernote.org/">http://summernote.org/</a>   summernote富文本</li><li><a href="http://www.open-open.com/">http://www.open-open.com</a>  open开放技术网站</li><li><a href="http://www.oschina.net/">http://www.oschina.net/</a></li><li><a href="http://www.csdn.net/">http://www.csdn.net</a></li><li><a href="http://vdisk.weibo.com/s/sUdlABIlyjroS">http://vdisk.weibo.com/s/sUdlABIlyjroS</a> 新浪书籍网址</li><li><a href="http://tool.oschina.net/apidocs/">http://tool.oschina.net/apidocs/</a>  在线api文档</li><li><a href="https://www.processon.com/diagrams">https://www.processon.com/diagrams</a>   在线画图工具ProcessOn</li><li><a href="http://madmin.swlabs.co/">http://madmin.swlabs.co/</a> bootstrap后台模板        </li><li><a href="http://issues.wenzhixin.net.cn/bootstrap-table/">http://issues.wenzhixin.net.cn/bootstrap-table/</a>  boostrap table参考    </li><li><a href="http://issues.wenzhixin.net.cn/bootstrap-table/#methods/scrollTo.html">http://issues.wenzhixin.net.cn/bootstrap-table/#methods/scrollTo.html</a>  boostrap 固定表头的tabl   </li><li><a href="http://www.runoob.com/try/demo_source/bootstrap-glyph-customization.htm">http://www.runoob.com/try/demo_source/bootstrap-glyph-customization.htm</a>  bootstrap的icon图标</li><li><a href="http://repo.spring.io/release/org/springframework/spring/">http://repo.spring.io/release/org/springframework/spring/</a> spring官网 jar包下载</li><li><a href="http://www.gooseeker.com/cn/node/">http://www.gooseeker.com/cn/node/</a>  搜客  开源网络爬虫</li><li><a href="https://curl.haxx.se/download.html">https://curl.haxx.se/download.html</a>  curl 工具下载 安装前先安装  <a href="http://slproweb.com/products/Win32OpenSSL.html">http://slproweb.com/products/Win32OpenSSL.html</a></li><li><a href="http://www.mongovue.com/">http://www.mongovue.com/</a>  mongodb 可视化工具 mongovue 下载</li><li><a href="https://www.mongodb.org/dl/win32">https://www.mongodb.org/dl/win32</a>  mongodb软件下载</li><li><a href="https://www.mongodb.com/">https://www.mongodb.com</a>  mongodb 下载</li><li><a href="https://archive.apache.org/dist/maven/maven-3/">https://archive.apache.org/dist/maven/maven-3/</a> 各种版本maven下载</li><li><a href="http://maven.apache.org/download.cgi">http://maven.apache.org/download.cgi</a>  maven 官网，下载地址， 里面有maven对应的jdk版本</li><li><a href="http://www.boyunjian.com/">http://www.boyunjian.com/</a>  拔云剑   源码网站  </li><li><a href="http://www.21cto.com/">http://www.21cto.com/</a>  21CTO社区</li><li><a href="http://36kr.com/">http://36kr.com/</a> 创业者咨询</li><li><a href="http://36kr.com/p/5045631.html">http://36kr.com/p/5045631.html</a>  产品经理」和「功能经理」的区别</li><li><a href="http://www.jikexueyuan.com/">http://www.jikexueyuan.com/</a>      极客学习网站</li><li><a href="http://maven.oschina.net/">http://maven.oschina.net</a>  开源中国Maven库</li><li><a href="http://note.youdao.com/">http://note.youdao.com/</a>  有道云笔记</li><li><a href="http://www.open-open.com/">http://www.open-open.com</a></li><li><a href="http://www.jiagoushuo.com/">http://www.jiagoushuo.com/</a>  架构说</li><li><a href="http://www.hx95.com/">http://www.hx95.com/</a>   黑客网站</li><li><a href="http://abbr.dict.cn/Statistics/Stats">http://abbr.dict.cn/Statistics/Stats</a>  英文简称网站</li><li><a href="http://bbs.9ria.com/thread-44875-1-1.html">http://bbs.9ria.com/thread-44875-1-1.html</a>   flex学习</li><li><a href="http://www.cn-java.com/www1/?action-login">http://www.cn-java.com/www1/?action-login</a> java学习：中文java技术网</li><li><a href="http://www.docin.com/list.html">http://www.docin.com/list.html</a>  豆丁学习</li><li><a href="http://home.51cto.com/index.php?s=/space/5084129">http://home.51cto.com/index.php?s=/space/5084129</a>  51CTO首页</li><li><a href="http://ishare.iask.sina.com.cn/download/explain.php?fileid=22242016">http://ishare.iask.sina.com.cn/download/explain.php?fileid=22242016</a></li><li><a href="http://www.56.com/u74/v_ODM2MDUxNjc.html">http://www.56.com/u74/v_ODM2MDUxNjc.html</a></li><li><a href="http://www.cn-java.com/www1/struts.php">http://www.cn-java.com/www1/struts.php</a>   中文Java技术网</li><li><a href="http://115.com/">http://115.com/</a></li><li><a href="http://www.apache.org/dist/httpd/binaries/win32/">http://www.apache.org/dist/httpd/binaries/win32/</a>  apache  下载地址</li><li><a href="http://www.cnblogs.com/">www.cnblogs.com</a>    博客园</li><li><a href="http://engineeredweb.com/blog/09/12/preloading-images-jquery-and-javascript/">http://engineeredweb.com/blog/09/12/preloading-images-jquery-and-javascript/</a> jquery 国外网址</li><li><a href="https://code.google.com/">https://code.google.com</a>              google代码集</li><li><a href="http://www.2cto.com/">http://www.2cto.com</a></li><li><a href="http://struts.apache.org/release/2.0.x/index.html">http://struts.apache.org/release/2.0.x/index.html</a>   struts2 官网</li><li><a href="http://www.linuxidc.com/">http://www.linuxidc.com</a></li><li><a href="https://github.com/jobbole/awesome-python-cn">https://github.com/jobbole/awesome-python-cn</a>  python 学习汇总网站</li></ul><h2 id="资源库"><a href="#资源库" class="headerlink" title="资源库"></a>资源库</h2><ol><li>无敌全能综合导航站</li></ol><ul><li>虫部落（聚合搜索平台） <a href="https://search.chongbuluo.com/">https://search.chongbuluo.com</a></li><li>科塔学术（专业学术导航） <a href="https://site.sciping.com/">https://site.sciping.com</a></li><li>码力全开（设计资源导航） <a href="https://design.maliquankai.com/">https://design.maliquankai.com</a></li><li>SeeSeed（设计资源导航2） <a href="https://www.seeseed.com/">https://www.seeseed.com</a></li><li>书享家（电子书资源导航） <a href="http://shuxiangjia.cn/">http://shuxiangjia.cn</a></li><li>HiPPTer（PPT资源导航） <a href="https://www.hippter.com/">https://www.hippter.com</a></li></ul><ol start="2"><li>学习资源</li></ol><ul><li>哔哩哔哩（直接吹爆） <a href="https://www.bilibili.com/">https://www.bilibili.com</a></li><li>第一教程网（课程学习资源） <a href="https://www.diyijc.com/">https://www.diyijc.com</a></li><li>北大教材（精品学习教材） <a href="https://pup6.yunzhan365.com/bookcase/jmyr/index.html">https://pup6.yunzhan365.com/bookcase/jmyr/index.html</a></li><li>考试酷（百万题库网站） <a href="https://www.examcoo.com/index/ku">https://www.examcoo.com/index/ku</a></li><li>外语慕课平台（外语学习好渠道） <a href="https://moocs.unipus.cn/">https://moocs.unipus.cn</a></li></ul><ol start="3"><li>图片视频资源</li></ol><ul><li>Wallhaven（宝藏壁纸网站） <a href="https://wallhaven.cc/">https://wallhaven.cc</a></li><li>Unsplash（优质图片资源） <a href="https://unsplash.com/">https://unsplash.com</a></li><li>Videvo（精品视频图片资源） <a href="https://www.videvo.net/">https://www.videvo.net</a></li><li>Pixabay（图片视频音频图形插画） <a href="https://pixabay.com/">https://pixabay.com</a></li><li>Pexels（老牌图库网站） <a href="https://www.pexels.com/zh-cn">https://www.pexels.com/zh-cn</a></li></ul><ol start="4"><li>稀缺宝藏资源</li></ol><ul><li>千米游戏（全是爷青回游戏） <a href="https://www.yikm.net/">https://www.yikm.net</a></li><li>AirPano（云旅游-巨爽） <a href="https://www.airpano.org.cn/">https://www.airpano.org.cn</a></li><li>Upanso（优质度盘搜索） <a href="https://upanso.com/">https://upanso.com</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;技术工具&quot;&gt;&lt;a href=&quot;#技术工具&quot; class=&quot;headerlink&quot; title=&quot;技术工具&quot;&gt;&lt;/a&gt;技术工具&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;http://sfz.uzuzuz.com/&quot;&gt;身份证生成器&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a </summary>
      
    
    
    
    <category term="技术工具" scheme="https://nydia.github.io/categories/%E6%8A%80%E6%9C%AF%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="技术工具" scheme="https://nydia.github.io/tags/%E6%8A%80%E6%9C%AF%E5%B7%A5%E5%85%B7/"/>
    
  </entry>
  
  <entry>
    <title>面试题集</title>
    <link href="https://nydia.github.io/2022/10/01/interview/README/"/>
    <id>https://nydia.github.io/2022/10/01/interview/README/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T12:52:45.176Z</updated>
    
    <content type="html"><![CDATA[<h2 id="题库"><a href="#题库" class="headerlink" title="题库"></a>题库</h2><ul><li><a href="https://cloud.tencent.com/developer/article/1812085">JVM虚拟机面试题</a></li><li><a href="https://github.com/qinxuewu/docs/tree/master/docs">一个可以参考的学习之路</a></li><li><a href="https://www.cnblogs.com/chen1005/p/10481102.html">Java 208道面试题及部分答案</a></li><li><a href="https://mp.weixin.qq.com/s/dtsBOBPm59fabce0S4tKUA">阿里-面试题系列：JVM 夺命连环10问</a></li><li><a href="https://mp.weixin.qq.com/s/pIu82Ndob6tapw8B0Wgn-A">阿里-面试题系列：Linux 夺命连环5问</a></li><li><a href="https://mp.weixin.qq.com/s/srIgoFyW9j3z-QnyUPPaGg">阿里-面试题系列：Zookeeper 夺命连环9问</a></li><li><a href="https://mp.weixin.qq.com/s/nBatZCMmt6_48g6SBpXK1g">阿里-面试题系列：MQ 夺命连环11问</a></li><li><a href="https://mp.weixin.qq.com/s/dhywrsaEbX5GfMsm8Xs2aA">阿里-面试题系列：Dubbo 夺命连环9问</a></li><li><a href="https://mp.weixin.qq.com/s/1fzVVw4dqjXO97ZcMnICqw">阿里-面试题系列：分布式夺命连环9问</a></li><li><a href="https://mp.weixin.qq.com/s/UASAEiGWrTUnSaG81eC27w">阿里-面试题系列：Java 夺命连环16问</a></li><li><a href="https://mp.weixin.qq.com/s/-qYf8BNJVWDOcrQbJglWzw">阿里-面试题系列：Redis 夺命连环11问</a></li><li><a href="https://mp.weixin.qq.com/s/JEQ9ha9NhZ-k3SUNlRx8Jw">阿里-面试题系列：Spring 夺命连环10问</a></li><li><a href="https://mp.weixin.qq.com/s/RjJv4uRrqaCg5jqijAG0BA">阿里-面试题系列：Mysql 夺命连环13问</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;题库&quot;&gt;&lt;a href=&quot;#题库&quot; class=&quot;headerlink&quot; title=&quot;题库&quot;&gt;&lt;/a&gt;题库&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.tencent.com/developer/article/1812085&quot;&gt;JV</summary>
      
    
    
    
    <category term="面试" scheme="https://nydia.github.io/categories/%E9%9D%A2%E8%AF%95/"/>
    
    
    <category term="面试题" scheme="https://nydia.github.io/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    
  </entry>
  
  <entry>
    <title>博客目录</title>
    <link href="https://nydia.github.io/2022/10/01/index/index/"/>
    <id>https://nydia.github.io/2022/10/01/index/index/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T12:51:02.147Z</updated>
    
    <content type="html"><![CDATA[<h2 id="RocketMQ"><a href="#RocketMQ" class="headerlink" title="RocketMQ"></a>RocketMQ</h2><ul><li><p><a href="../rocketmq-docs/cn/README.md">rocketmq中文文档</a></p></li><li><p><a href="../rocketmq-docs/en/README.md">rocketmq英文文档</a></p></li></ul><h2 id="Flutter介绍"><a href="#Flutter介绍" class="headerlink" title="Flutter介绍"></a>Flutter介绍</h2><ul><li><a href="../flutter/flutter.md">flutter简单介绍</a></li></ul><h2 id="ShardingSphere"><a href="#ShardingSphere" class="headerlink" title="ShardingSphere"></a>ShardingSphere</h2><ul><li><a href="https://shardingsphere.apache.org/document/current/cn/overview/">ShardingSphere中文文档</a></li></ul><h2 id="工具类网站"><a href="#工具类网站" class="headerlink" title="工具类网站"></a>工具类网站</h2><ul><li><a href="../tech-tools/README.md">工具类网站</a></li></ul><h2 id="技术书单"><a href="#技术书单" class="headerlink" title="技术书单"></a>技术书单</h2><ul><li><a href="../books/java/README.md">Java必读书单</a></li><li><a href="../books/architect/README.md">架构师必读书单</a></li></ul><h2 id="面试题集"><a href="#面试题集" class="headerlink" title="面试题集"></a>面试题集</h2><ul><li><a href="../interview/README.md">面试题</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;RocketMQ&quot;&gt;&lt;a href=&quot;#RocketMQ&quot; class=&quot;headerlink&quot; title=&quot;RocketMQ&quot;&gt;&lt;/a&gt;RocketMQ&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;../rocketmq-docs/cn/README.m</summary>
      
    
    
    
    <category term="博客目录" scheme="https://nydia.github.io/categories/%E5%8D%9A%E5%AE%A2%E7%9B%AE%E5%BD%95/"/>
    
    
    <category term="博客目录" scheme="https://nydia.github.io/tags/%E5%8D%9A%E5%AE%A2%E7%9B%AE%E5%BD%95/"/>
    
  </entry>
  
  <entry>
    <title>Java书单</title>
    <link href="https://nydia.github.io/2022/10/01/books/java/README/"/>
    <id>https://nydia.github.io/2022/10/01/books/java/README/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T11:40:01.603Z</updated>
    
    <content type="html"><![CDATA[<h1 id="JavaBooks"><a href="#JavaBooks" class="headerlink" title="JavaBooks"></a>JavaBooks</h1><ul><li>推荐书单</li></ul><h2 id="书单"><a href="#书单" class="headerlink" title="书单"></a>书单</h2><h3 id="01-JVM"><a href="#01-JVM" class="headerlink" title="01.JVM"></a>01.JVM</h3><ul><li>深入理解Java虚拟机</li></ul><h3 id="02-NIO"><a href="#02-NIO" class="headerlink" title="02.NIO"></a>02.NIO</h3><ul><li>Netty实战</li><li>Netty权威指南</li></ul><h3 id="03-Java并发编程"><a href="#03-Java并发编程" class="headerlink" title="03.Java并发编程"></a>03.Java并发编程</h3><ul><li>Java并发编程实践</li></ul><h3 id="04-技术框架"><a href="#04-技术框架" class="headerlink" title="04.技术框架"></a>04.技术框架</h3><ul><li>Spring 实战</li><li>Spring Boot编程思想</li></ul><h3 id="05-数据库"><a href="#05-数据库" class="headerlink" title="05.数据库"></a>05.数据库</h3><ul><li>高可用MySQL</li><li>高性能MySQL</li></ul><h3 id="06-分库分表"><a href="#06-分库分表" class="headerlink" title="06.分库分表"></a>06.分库分表</h3><ul><li><a href="https://shardingsphere.apache.org/pdf/shardingsphere_docs_cn.pdf">https://shardingsphere.apache.org/pdf/shardingsphere_docs_cn.pdf</a></li></ul><h3 id="07-RPC与微服务"><a href="#07-RPC与微服务" class="headerlink" title="07.RPC与微服务"></a>07.RPC与微服务</h3><ul><li>深度剖析Apache Dubbo核心技术内幕</li></ul><h3 id="08-分布式缓存"><a href="#08-分布式缓存" class="headerlink" title="08.分布式缓存"></a>08.分布式缓存</h3><ul><li>深入分布式缓存：从原理到实践</li></ul><h3 id="09-分布式消息"><a href="#09-分布式消息" class="headerlink" title="09.分布式消息"></a>09.分布式消息</h3><ul><li>分布式消息中间件实践</li></ul><h3 id="10-分布式架构设计"><a href="#10-分布式架构设计" class="headerlink" title="10.分布式架构设计"></a>10.分布式架构设计</h3><ul><li>数据密集型应用系统设计</li><li>高可用架构，高可用可伸缩微服务架构：基于Dubbo、Spring Cloud和Service Mesh</li><li>微服务架构设计模式</li></ul><h3 id="11-重构与系统改造"><a href="#11-重构与系统改造" class="headerlink" title="11.重构与系统改造"></a>11.重构与系统改造</h3><ul><li>大型网站技术架构演进与性能优化</li><li>发布！设计与部署稳定的分布式系统</li><li>重构：改善既有代码的设计</li></ul><h3 id="12-程序员成长"><a href="#12-程序员成长" class="headerlink" title="12.程序员成长"></a>12.程序员成长</h3><ul><li>高效程序员的45个习惯</li></ul><h3 id="99-其他"><a href="#99-其他" class="headerlink" title="99.其他"></a>99.其他</h3><h2 id="详细推荐书单"><a href="#详细推荐书单" class="headerlink" title="详细推荐书单"></a>详细推荐书单</h2><h4 id="基础入门"><a href="#基础入门" class="headerlink" title="基础入门"></a>基础入门</h4><p>Java编程思想(Thinking in Java)、Java核心技术(Core Java) ，Java8 实战(Java in action)，Effective Java中文版，代码大全</p><h4 id="框架入门与深入"><a href="#框架入门与深入" class="headerlink" title="框架入门与深入"></a>框架入门与深入</h4><p>Spring实战(Spring in action)，Hibernate实战（Java Persistence with Hibernate），深入分析Java Web技术内幕，互联网轻量级SSM框架解密，MyBatis 3源码深度解析，Spring技术内幕，Spring Boot技术内幕，Spring微服务实战</p><h4 id="NIO与并发编程"><a href="#NIO与并发编程" class="headerlink" title="NIO与并发编程"></a>NIO与并发编程</h4><p>Java并发编程实战（Java Concurrency in Practice），Java并发编程的艺术，Java异步编程实战，Netty实战，Netty权威指南，NIO与Socket编程技术指南，</p><h4 id="JVM与性能"><a href="#JVM与性能" class="headerlink" title="JVM与性能"></a>JVM与性能</h4><p>深入理解Java虚拟机，深入理解JVM字节码，Java性能优化权威指南，Java性能权威指南，性能之巅，高可用MySQL，高性能MySQL</p><h4 id="分布式与中间件"><a href="#分布式与中间件" class="headerlink" title="分布式与中间件"></a>分布式与中间件</h4><p>分布式系统：概念与设计，分布式消息中间件实践，分布式系统应用设计，数据密集型应用系统设计，分布式服务架构：原理、设计与实战，分布式系统架构，深入分布式缓存：从原理到实践，Kafka权威指南，深入理解Kafka：核心设计与实践原理，Apache Kafka源码剖析，RocketMQ技术内幕：RocketMQ架构设计与实现原理，深度剖析Apache Dubbo核心技术内幕，Redis实战，</p><h4 id="架构设计与互联网技术"><a href="#架构设计与互联网技术" class="headerlink" title="架构设计与互联网技术"></a>架构设计与互联网技术</h4><p>系统架构：复杂系统的产品设计与开发，软件架构，分布式系统架构：技术栈详解与快速进阶，亿级流量网站架构核心技术，京东基础架构建设之路，企业IT架构转型之道：阿里巴巴中台战略思想与架构实战，高可用架构，高可用可伸缩微服务架构：基于Dubbo、Spring Cloud和Service Mesh，架构之美，架构简介之道，大型网站技术架构演进与性能优化，发布！设计与部署稳定的分布式系统，微服务架构设计模式，微服务设计，生产微服务，软件系统架构：使用视点和视角与利益相关者合 ，DevOps 软件架构师行动指南，DevOps 实践指南，企业应用架构模式，企业集成模式</p><h4 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h4><p>图解设计模式、图解HTTP协议、图解性能优化，重构：改善既有代码的设计，混沌工程，深入理解计算机系统，数据库全书，虚拟机：系统与进程的通用平台，程序员的职业素养，数学之美，算法之美，系统之美，计算进化史，人月神话，编程珠玑，高效程序员的45个习惯：敏捷开发修炼之道，算法导论，计算机程序设计艺术，程序员修炼之道，有效的单元测试</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;JavaBooks&quot;&gt;&lt;a href=&quot;#JavaBooks&quot; class=&quot;headerlink&quot; title=&quot;JavaBooks&quot;&gt;&lt;/a&gt;JavaBooks&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;推荐书单&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;书单&quot;&gt;&lt;a href=</summary>
      
    
    
    
    <category term="Java书单" scheme="https://nydia.github.io/categories/Java%E4%B9%A6%E5%8D%95/"/>
    
    
    <category term="书单" scheme="https://nydia.github.io/tags/%E4%B9%A6%E5%8D%95/"/>
    
  </entry>
  
  <entry>
    <title>架构师书单</title>
    <link href="https://nydia.github.io/2022/10/01/books/architect/README/"/>
    <id>https://nydia.github.io/2022/10/01/books/architect/README/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T11:21:14.198Z</updated>
    
    <content type="html"><![CDATA[<h1 id="架构师书单"><a href="#架构师书单" class="headerlink" title="架构师书单"></a>架构师书单</h1><p><strong>第一步重要的 linux编程艺术 、 unix编程艺术</strong></p><h2 id="成长篇"><a href="#成长篇" class="headerlink" title="成长篇"></a>成长篇</h2><p>《异类》<br>《随机漫步的傻瓜》<br>《一万小时天才理论》<br>《情商》<br>《优秀到不能被忽视》<br>《影响力大师》<br>《优化你的两个小时》<br>《有效学习》(微信读书)（推荐理由： 关于学习的书籍，我看过很多本，觉得最有科学指导意义的就是《有效学习》这本）</p><p>《如何系统的思考》（偏重实践）</p><p>《系统思考》（偏重理论）</p><h3 id="与程序员沟通、向上管理、表达能力"><a href="#与程序员沟通、向上管理、表达能力" class="headerlink" title="与程序员沟通、向上管理、表达能力"></a>与程序员沟通、向上管理、表达能力</h3><p>与程序员沟通、向上管理、表达能力（比如如何把一个技术点、项目方案给大家讲清楚）以及技术视野提升和架构思维等主题相关。 秦老师有什么书建议吗？ 见附件非技术书。最推荐的几本： 暗时间，告诉你很多你看不清的事实。 金字塔原理，把结构化思维和表达说明白了。 非暴力沟通，把怎么有效沟通跟其他情况区分开了。</p><p>暗时间.pdf</p><p>金字塔原理_高清版.pdf</p><p>麦肯锡方法完整版.pdf</p><p>《原则》中文版.pdf</p><p>领导者的优势：掌握思维突破点的5个技巧.pdf</p><p>重新定义公司.埃里克•施密特,乔纳森•罗森伯格.pdf</p><p>职场新人的典型问题.pdf</p><p>沟通技巧和方法培训PPT.pptx</p><p>非暴力沟通+完整中文版.pdf</p><h2 id="技术篇"><a href="#技术篇" class="headerlink" title="技术篇"></a>技术篇</h2><p>学习的思路：</p><ol><li><p>深度学习你的代码运行环境：例如 Linux 程序员一定要深入学习 Linux 和 UNIX 的操作系统，iOS 程序员要深入学习 iOS 系统，前端程序员要深入学习浏览器原理，以此类推。</p></li><li><p>深入学习你的核心工具：例如 Java 程序员的核心工具是 Java，嵌入式程序员是 C，而 DBA 就不是学编程语言，而是学 MySQL 或者 Oracle 了。</p></li><li><p>深度学习领域基础知识：例如后端程序员的网络编程，前端程序员的动效知识，Android 客户端程序员的渲染知识，以及所有程序员都要求的算法知识等。</p></li><li><p>广泛学习技术领域的通用成熟技术：例如前端程序员要学的 React 和 Vue，Java 程序员要学的 Netty、Spring，互联网后端程序员的标配 MySQL、Redis 等。下面我以 Linux 后端 Java 程序员为例，给你推荐相关技术书籍。</p></li></ol><p>《UNIX 编程艺术》<br>《UNIX 网络编程（卷 1）》<br>《UNIX 环境高级编程》<br>《Linux 系统编程》<br>《TCP&#x2F;IP 详解（卷 1）》<br>《算法之美》<br>《算法设计与应用》<br>《Java 编程思想》<br>《深入理解 Java 虚拟机》<br>《C++ Primer》<br>《增长黑客》<br>《需求》<br>《淘宝十年产品事》<br>《定位》<br>《宝洁制胜战略》</p><h2 id="业务篇"><a href="#业务篇" class="headerlink" title="业务篇"></a>业务篇</h2><h3 id="支付"><a href="#支付" class="headerlink" title="支付"></a>支付</h3><p>《支付方法论》</p><p>《一本书读懂支付》</p><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><h3 id="人工智能学习方向（AI"><a href="#人工智能学习方向（AI" class="headerlink" title="人工智能学习方向（AI)"></a>人工智能学习方向（AI)</h3><ul><li><p>《机器学习实战》</p></li><li><p>《机器学习实战》  </p><p><a href="https://cloud.tencent.com/developer/article/1512788">https://cloud.tencent.com/developer/article/1512788</a></p></li></ul><h3 id="大数据学习方向"><a href="#大数据学习方向" class="headerlink" title="大数据学习方向"></a>大数据学习方向</h3><ul><li>《实战大数据》</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;架构师书单&quot;&gt;&lt;a href=&quot;#架构师书单&quot; class=&quot;headerlink&quot; title=&quot;架构师书单&quot;&gt;&lt;/a&gt;架构师书单&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;第一步重要的 linux编程艺术 、 unix编程艺术&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;成</summary>
      
    
    
    
    <category term="书单" scheme="https://nydia.github.io/categories/%E4%B9%A6%E5%8D%95/"/>
    
    
    <category term="架构师书单" scheme="https://nydia.github.io/tags/%E6%9E%B6%E6%9E%84%E5%B8%88%E4%B9%A6%E5%8D%95/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ样例</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/cn/RocketMQ_Example/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/cn/RocketMQ_Example/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T12:58:13.104Z</updated>
    
    <content type="html"><![CDATA[<h1 id="样例"><a href="#样例" class="headerlink" title="样例"></a>样例</h1><hr><h2 id="1-基本样例"><a href="#1-基本样例" class="headerlink" title="1 基本样例"></a>1 基本样例</h2><p>在基本样例中我们提供如下的功能场景：</p><ul><li>使用RocketMQ发送三种类型的消息：同步消息、异步消息和单向消息。其中前两种消息是可靠的，因为会有发送是否成功的应答。</li><li>使用RocketMQ来消费接收到的消息。</li></ul><h3 id="1-1-加入依赖："><a href="#1-1-加入依赖：" class="headerlink" title="1.1 加入依赖："></a>1.1 加入依赖：</h3><p><code>maven:</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.apache.rocketmq&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;rocketmq-client&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;4.3.0&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><p><code>gradle</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">compile &#x27;org.apache.rocketmq:rocketmq-client:4.3.0&#x27;</span><br></pre></td></tr></table></figure><h3 id="1-2-消息发送"><a href="#1-2-消息发送" class="headerlink" title="1.2 消息发送"></a>1.2 消息发送</h3><h4 id="1、Producer端发送同步消息"><a href="#1、Producer端发送同步消息" class="headerlink" title="1、Producer端发送同步消息"></a>1、Producer端发送同步消息</h4><p>这种可靠性同步地发送方式使用的比较广泛，比如：重要的消息通知，短信通知。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SyncProducer</span> &#123;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="comment">// 实例化消息生产者Producer</span></span><br><span class="line">        <span class="type">DefaultMQProducer</span> <span class="variable">producer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQProducer</span>(<span class="string">&quot;please_rename_unique_group_name&quot;</span>);</span><br><span class="line">    <span class="comment">// 设置NameServer的地址</span></span><br><span class="line">    producer.setNamesrvAddr(<span class="string">&quot;localhost:9876&quot;</span>);</span><br><span class="line">    <span class="comment">// 启动Producer实例</span></span><br><span class="line">        producer.start();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">        <span class="comment">// 创建消息，并指定Topic，Tag和消息体</span></span><br><span class="line">        <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Message</span>(<span class="string">&quot;TopicTest&quot;</span> <span class="comment">/* Topic */</span>,</span><br><span class="line">        <span class="string">&quot;TagA&quot;</span> <span class="comment">/* Tag */</span>,</span><br><span class="line">        (<span class="string">&quot;Hello RocketMQ &quot;</span> + i).getBytes(RemotingHelper.DEFAULT_CHARSET) <span class="comment">/* Message body */</span></span><br><span class="line">        );</span><br><span class="line">        <span class="comment">// 发送消息到一个Broker</span></span><br><span class="line">            <span class="type">SendResult</span> <span class="variable">sendResult</span> <span class="operator">=</span> producer.send(msg);</span><br><span class="line">            <span class="comment">// 通过sendResult返回消息是否成功送达</span></span><br><span class="line">            System.out.printf(<span class="string">&quot;%s%n&quot;</span>, sendResult);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果不再发送消息，关闭Producer实例。</span></span><br><span class="line">    producer.shutdown();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2、发送异步消息"><a href="#2、发送异步消息" class="headerlink" title="2、发送异步消息"></a>2、发送异步消息</h4><p>异步消息通常用在对响应时间敏感的业务场景，即发送端不能容忍长时间地等待Broker的响应。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AsyncProducer</span> &#123;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="comment">// 实例化消息生产者Producer</span></span><br><span class="line">        <span class="type">DefaultMQProducer</span> <span class="variable">producer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQProducer</span>(<span class="string">&quot;please_rename_unique_group_name&quot;</span>);</span><br><span class="line">    <span class="comment">// 设置NameServer的地址</span></span><br><span class="line">        producer.setNamesrvAddr(<span class="string">&quot;localhost:9876&quot;</span>);</span><br><span class="line">    <span class="comment">// 启动Producer实例</span></span><br><span class="line">        producer.start();</span><br><span class="line">        producer.setRetryTimesWhenSendAsyncFailed(<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">                <span class="keyword">final</span> <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> i;</span><br><span class="line">            <span class="comment">// 创建消息，并指定Topic，Tag和消息体</span></span><br><span class="line">                <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Message</span>(<span class="string">&quot;TopicTest&quot;</span>,</span><br><span class="line">                    <span class="string">&quot;TagA&quot;</span>,</span><br><span class="line">                    <span class="string">&quot;OrderID188&quot;</span>,</span><br><span class="line">                    <span class="string">&quot;Hello world&quot;</span>.getBytes(RemotingHelper.DEFAULT_CHARSET));</span><br><span class="line">                <span class="comment">// SendCallback接收异步返回结果的回调</span></span><br><span class="line">                producer.send(msg, <span class="keyword">new</span> <span class="title class_">SendCallback</span>() &#123;</span><br><span class="line">                    <span class="meta">@Override</span></span><br><span class="line">                    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onSuccess</span><span class="params">(SendResult sendResult)</span> &#123;</span><br><span class="line">                        System.out.printf(<span class="string">&quot;%-10d OK %s %n&quot;</span>, index,</span><br><span class="line">                            sendResult.getMsgId());</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="meta">@Override</span></span><br><span class="line">                    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onException</span><span class="params">(Throwable e)</span> &#123;</span><br><span class="line">                    System.out.printf(<span class="string">&quot;%-10d Exception %s %n&quot;</span>, index, e);</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                    &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果不再发送消息，关闭Producer实例。</span></span><br><span class="line">    producer.shutdown();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3、单向发送消息"><a href="#3、单向发送消息" class="headerlink" title="3、单向发送消息"></a>3、单向发送消息</h4><p>这种方式主要用在不特别关心发送结果的场景，例如日志发送。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OnewayProducer</span> &#123;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line">    <span class="comment">// 实例化消息生产者Producer</span></span><br><span class="line">        <span class="type">DefaultMQProducer</span> <span class="variable">producer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQProducer</span>(<span class="string">&quot;please_rename_unique_group_name&quot;</span>);</span><br><span class="line">    <span class="comment">// 设置NameServer的地址</span></span><br><span class="line">        producer.setNamesrvAddr(<span class="string">&quot;localhost:9876&quot;</span>);</span><br><span class="line">    <span class="comment">// 启动Producer实例</span></span><br><span class="line">        producer.start();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">        <span class="comment">// 创建消息，并指定Topic，Tag和消息体</span></span><br><span class="line">        <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Message</span>(<span class="string">&quot;TopicTest&quot;</span> <span class="comment">/* Topic */</span>,</span><br><span class="line">                <span class="string">&quot;TagA&quot;</span> <span class="comment">/* Tag */</span>,</span><br><span class="line">                (<span class="string">&quot;Hello RocketMQ &quot;</span> + i).getBytes(RemotingHelper.DEFAULT_CHARSET) <span class="comment">/* Message body */</span></span><br><span class="line">        );</span><br><span class="line">        <span class="comment">// 发送单向消息，没有任何返回结果</span></span><br><span class="line">        producer.sendOneway(msg);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果不再发送消息，关闭Producer实例。</span></span><br><span class="line">    producer.shutdown();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-3-消费消息"><a href="#1-3-消费消息" class="headerlink" title="1.3 消费消息"></a>1.3 消费消息</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Consumer</span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException, MQClientException &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 实例化消费者</span></span><br><span class="line">        <span class="type">DefaultMQPushConsumer</span> <span class="variable">consumer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQPushConsumer</span>(<span class="string">&quot;please_rename_unique_group_name&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置NameServer的地址</span></span><br><span class="line">        consumer.setNamesrvAddr(<span class="string">&quot;localhost:9876&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 订阅一个或者多个Topic，以及Tag来过滤需要消费的消息</span></span><br><span class="line">        consumer.subscribe(<span class="string">&quot;TopicTest&quot;</span>, <span class="string">&quot;*&quot;</span>);</span><br><span class="line">    <span class="comment">// 注册回调实现类来处理从broker拉取回来的消息</span></span><br><span class="line">        consumer.registerMessageListener(<span class="keyword">new</span> <span class="title class_">MessageListenerConcurrently</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> ConsumeConcurrentlyStatus <span class="title function_">consumeMessage</span><span class="params">(List&lt;MessageExt&gt; msgs, ConsumeConcurrentlyContext context)</span> &#123;</span><br><span class="line">                System.out.printf(<span class="string">&quot;%s Receive New Messages: %s %n&quot;</span>, Thread.currentThread().getName(), msgs);</span><br><span class="line">                <span class="comment">// 标记该消息已经被成功消费</span></span><br><span class="line">                <span class="keyword">return</span> ConsumeConcurrentlyStatus.CONSUME_SUCCESS;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">// 启动消费者实例</span></span><br><span class="line">        consumer.start();</span><br><span class="line">        System.out.printf(<span class="string">&quot;Consumer Started.%n&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="2-顺序消息样例"><a href="#2-顺序消息样例" class="headerlink" title="2 顺序消息样例"></a>2 顺序消息样例</h2><p>消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序，可以分为分区有序或者全局有序。</p><p>顺序消费的原理解析，在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列)；而消费消息的时候从多个queue上拉取消息，这种情况发送和消费是不能保证顺序。但是如果控制发送的顺序消息只依次发送到同一个queue中，消费的时候只从这个queue上依次拉取，则就保证了顺序。当发送和消费参与的queue只有一个，则是全局有序；如果多个queue参与，则为分区有序，即相对每个queue，消息都是有序的。</p><p>下面用订单进行分区有序的示例。一个订单的顺序流程是：创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中，消费时，同一个OrderId获取到的肯定是同一个队列。</p><h3 id="2-1-顺序消息生产"><a href="#2-1-顺序消息生产" class="headerlink" title="2.1 顺序消息生产"></a>2.1 顺序消息生产</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> org.apache.rocketmq.example.order2;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.producer.DefaultMQProducer;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.producer.MessageQueueSelector;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.producer.SendResult;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.message.Message;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.message.MessageQueue;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.text.SimpleDateFormat;</span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.Date;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* Producer，发送顺序消息</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Producer</span> &#123;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">       <span class="type">DefaultMQProducer</span> <span class="variable">producer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQProducer</span>(<span class="string">&quot;please_rename_unique_group_name&quot;</span>);</span><br><span class="line"></span><br><span class="line">       producer.setNamesrvAddr(<span class="string">&quot;127.0.0.1:9876&quot;</span>);</span><br><span class="line"></span><br><span class="line">       producer.start();</span><br><span class="line"></span><br><span class="line">       String[] tags = <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;<span class="string">&quot;TagA&quot;</span>, <span class="string">&quot;TagC&quot;</span>, <span class="string">&quot;TagD&quot;</span>&#125;;</span><br><span class="line"></span><br><span class="line">       <span class="comment">// 订单列表</span></span><br><span class="line">       List&lt;OrderStep&gt; orderList = <span class="keyword">new</span> <span class="title class_">Producer</span>().buildOrders();</span><br><span class="line"></span><br><span class="line">       <span class="type">Date</span> <span class="variable">date</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>();</span><br><span class="line">       <span class="type">SimpleDateFormat</span> <span class="variable">sdf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">&quot;yyyy-MM-dd HH:mm:ss&quot;</span>);</span><br><span class="line">       <span class="type">String</span> <span class="variable">dateStr</span> <span class="operator">=</span> sdf.format(date);</span><br><span class="line">       <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">           <span class="comment">// 加个时间前缀</span></span><br><span class="line">           <span class="type">String</span> <span class="variable">body</span> <span class="operator">=</span> dateStr + <span class="string">&quot; Hello RocketMQ &quot;</span> + orderList.get(i);</span><br><span class="line">           <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Message</span>(<span class="string">&quot;TopicTest&quot;</span>, tags[i % tags.length], <span class="string">&quot;KEY&quot;</span> + i, body.getBytes());</span><br><span class="line"></span><br><span class="line">           <span class="type">SendResult</span> <span class="variable">sendResult</span> <span class="operator">=</span> producer.send(msg, <span class="keyword">new</span> <span class="title class_">MessageQueueSelector</span>() &#123;</span><br><span class="line">               <span class="meta">@Override</span></span><br><span class="line">               <span class="keyword">public</span> MessageQueue <span class="title function_">select</span><span class="params">(List&lt;MessageQueue&gt; mqs, Message msg, Object arg)</span> &#123;</span><br><span class="line">                   <span class="type">Long</span> <span class="variable">id</span> <span class="operator">=</span> (Long) arg;  <span class="comment">//根据订单id选择发送queue</span></span><br><span class="line">                   <span class="type">long</span> <span class="variable">index</span> <span class="operator">=</span> id % mqs.size();</span><br><span class="line">                   <span class="keyword">return</span> mqs.get((<span class="type">int</span>) index);</span><br><span class="line">               &#125;</span><br><span class="line">           &#125;, orderList.get(i).getOrderId());<span class="comment">//订单id</span></span><br><span class="line"></span><br><span class="line">           System.out.println(String.format(<span class="string">&quot;SendResult status:%s, queueId:%d, body:%s&quot;</span>,</span><br><span class="line">               sendResult.getSendStatus(),</span><br><span class="line">               sendResult.getMessageQueue().getQueueId(),</span><br><span class="line">               body));</span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">       producer.shutdown();</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 订单的步骤</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">OrderStep</span> &#123;</span><br><span class="line">       <span class="keyword">private</span> <span class="type">long</span> orderId;</span><br><span class="line">       <span class="keyword">private</span> String desc;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getOrderId</span><span class="params">()</span> &#123;</span><br><span class="line">           <span class="keyword">return</span> orderId;</span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setOrderId</span><span class="params">(<span class="type">long</span> orderId)</span> &#123;</span><br><span class="line">           <span class="built_in">this</span>.orderId = orderId;</span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">public</span> String <span class="title function_">getDesc</span><span class="params">()</span> &#123;</span><br><span class="line">           <span class="keyword">return</span> desc;</span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setDesc</span><span class="params">(String desc)</span> &#123;</span><br><span class="line">           <span class="built_in">this</span>.desc = desc;</span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">       <span class="meta">@Override</span></span><br><span class="line">       <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">           <span class="keyword">return</span> <span class="string">&quot;OrderStep&#123;&quot;</span> +</span><br><span class="line">               <span class="string">&quot;orderId=&quot;</span> + orderId +</span><br><span class="line">               <span class="string">&quot;, desc=&#x27;&quot;</span> + desc + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">               <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 生成模拟订单数据</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="keyword">private</span> List&lt;OrderStep&gt; <span class="title function_">buildOrders</span><span class="params">()</span> &#123;</span><br><span class="line">       List&lt;OrderStep&gt; orderList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;OrderStep&gt;();</span><br><span class="line"></span><br><span class="line">       <span class="type">OrderStep</span> <span class="variable">orderDemo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OrderStep</span>();</span><br><span class="line">       orderDemo.setOrderId(<span class="number">15103111039L</span>);</span><br><span class="line">       orderDemo.setDesc(<span class="string">&quot;创建&quot;</span>);</span><br><span class="line">       orderList.add(orderDemo);</span><br><span class="line"></span><br><span class="line">       orderDemo = <span class="keyword">new</span> <span class="title class_">OrderStep</span>();</span><br><span class="line">       orderDemo.setOrderId(<span class="number">15103111065L</span>);</span><br><span class="line">       orderDemo.setDesc(<span class="string">&quot;创建&quot;</span>);</span><br><span class="line">       orderList.add(orderDemo);</span><br><span class="line"></span><br><span class="line">       orderDemo = <span class="keyword">new</span> <span class="title class_">OrderStep</span>();</span><br><span class="line">       orderDemo.setOrderId(<span class="number">15103111039L</span>);</span><br><span class="line">       orderDemo.setDesc(<span class="string">&quot;付款&quot;</span>);</span><br><span class="line">       orderList.add(orderDemo);</span><br><span class="line"></span><br><span class="line">       orderDemo = <span class="keyword">new</span> <span class="title class_">OrderStep</span>();</span><br><span class="line">       orderDemo.setOrderId(<span class="number">15103117235L</span>);</span><br><span class="line">       orderDemo.setDesc(<span class="string">&quot;创建&quot;</span>);</span><br><span class="line">       orderList.add(orderDemo);</span><br><span class="line"></span><br><span class="line">       orderDemo = <span class="keyword">new</span> <span class="title class_">OrderStep</span>();</span><br><span class="line">       orderDemo.setOrderId(<span class="number">15103111065L</span>);</span><br><span class="line">       orderDemo.setDesc(<span class="string">&quot;付款&quot;</span>);</span><br><span class="line">       orderList.add(orderDemo);</span><br><span class="line"></span><br><span class="line">       orderDemo = <span class="keyword">new</span> <span class="title class_">OrderStep</span>();</span><br><span class="line">       orderDemo.setOrderId(<span class="number">15103117235L</span>);</span><br><span class="line">       orderDemo.setDesc(<span class="string">&quot;付款&quot;</span>);</span><br><span class="line">       orderList.add(orderDemo);</span><br><span class="line"></span><br><span class="line">       orderDemo = <span class="keyword">new</span> <span class="title class_">OrderStep</span>();</span><br><span class="line">       orderDemo.setOrderId(<span class="number">15103111065L</span>);</span><br><span class="line">       orderDemo.setDesc(<span class="string">&quot;完成&quot;</span>);</span><br><span class="line">       orderList.add(orderDemo);</span><br><span class="line"></span><br><span class="line">       orderDemo = <span class="keyword">new</span> <span class="title class_">OrderStep</span>();</span><br><span class="line">       orderDemo.setOrderId(<span class="number">15103111039L</span>);</span><br><span class="line">       orderDemo.setDesc(<span class="string">&quot;推送&quot;</span>);</span><br><span class="line">       orderList.add(orderDemo);</span><br><span class="line"></span><br><span class="line">       orderDemo = <span class="keyword">new</span> <span class="title class_">OrderStep</span>();</span><br><span class="line">       orderDemo.setOrderId(<span class="number">15103117235L</span>);</span><br><span class="line">       orderDemo.setDesc(<span class="string">&quot;完成&quot;</span>);</span><br><span class="line">       orderList.add(orderDemo);</span><br><span class="line"></span><br><span class="line">       orderDemo = <span class="keyword">new</span> <span class="title class_">OrderStep</span>();</span><br><span class="line">       orderDemo.setOrderId(<span class="number">15103111039L</span>);</span><br><span class="line">       orderDemo.setDesc(<span class="string">&quot;完成&quot;</span>);</span><br><span class="line">       orderList.add(orderDemo);</span><br><span class="line"></span><br><span class="line">       <span class="keyword">return</span> orderList;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-2-顺序消费消息"><a href="#2-2-顺序消费消息" class="headerlink" title="2.2 顺序消费消息"></a>2.2 顺序消费消息</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.message.MessageExt;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> org.apache.rocketmq.example.order2;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.consumer.ConsumeFromWhere;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.message.MessageExt;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.Random;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 顺序消息消费，带事务方式（应用可控制Offset什么时候提交）</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConsumerInOrder</span> &#123;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">       <span class="type">DefaultMQPushConsumer</span> <span class="variable">consumer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQPushConsumer</span>(<span class="string">&quot;please_rename_unique_group_name_3&quot;</span>);</span><br><span class="line">       consumer.setNamesrvAddr(<span class="string">&quot;127.0.0.1:9876&quot;</span>);</span><br><span class="line">       <span class="comment">/**</span></span><br><span class="line"><span class="comment">        * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费&lt;br&gt;</span></span><br><span class="line"><span class="comment">        * 如果非第一次启动，那么按照上次消费的位置继续消费</span></span><br><span class="line"><span class="comment">        */</span></span><br><span class="line">       consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);</span><br><span class="line"></span><br><span class="line">       consumer.subscribe(<span class="string">&quot;TopicTest&quot;</span>, <span class="string">&quot;TagA || TagC || TagD&quot;</span>);</span><br><span class="line"></span><br><span class="line">       consumer.registerMessageListener(<span class="keyword">new</span> <span class="title class_">MessageListenerOrderly</span>() &#123;</span><br><span class="line"></span><br><span class="line">           <span class="type">Random</span> <span class="variable">random</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line"></span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="keyword">public</span> ConsumeOrderlyStatus <span class="title function_">consumeMessage</span><span class="params">(List&lt;MessageExt&gt; msgs, ConsumeOrderlyContext context)</span> &#123;</span><br><span class="line">               context.setAutoCommit(<span class="literal">true</span>);</span><br><span class="line">               <span class="keyword">for</span> (MessageExt msg : msgs) &#123;</span><br><span class="line">                   <span class="comment">// 可以看到每个queue有唯一的consume线程来消费, 订单对每个queue(分区)有序</span></span><br><span class="line">                   System.out.println(<span class="string">&quot;consumeThread=&quot;</span> + Thread.currentThread().getName() + <span class="string">&quot;queueId=&quot;</span> + msg.getQueueId() + <span class="string">&quot;, content:&quot;</span> + <span class="keyword">new</span> <span class="title class_">String</span>(msg.getBody()));</span><br><span class="line">               &#125;</span><br><span class="line"></span><br><span class="line">               <span class="keyword">try</span> &#123;</span><br><span class="line">                   <span class="comment">//模拟业务逻辑处理中...</span></span><br><span class="line">                   TimeUnit.SECONDS.sleep(random.nextInt(<span class="number">10</span>));</span><br><span class="line">               &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                   e.printStackTrace();</span><br><span class="line">               &#125;</span><br><span class="line">               <span class="keyword">return</span> ConsumeOrderlyStatus.SUCCESS;</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;);</span><br><span class="line"></span><br><span class="line">       consumer.start();</span><br><span class="line"></span><br><span class="line">       System.out.println(<span class="string">&quot;Consumer Started.&quot;</span>);</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3-延时消息样例"><a href="#3-延时消息样例" class="headerlink" title="3 延时消息样例"></a>3 延时消息样例</h2><h3 id="3-1-启动消费者等待传入订阅消息"><a href="#3-1-启动消费者等待传入订阅消息" class="headerlink" title="3.1 启动消费者等待传入订阅消息"></a>3.1 启动消费者等待传入订阅消息</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.message.MessageExt;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ScheduledMessageConsumer</span> &#123;</span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">      <span class="comment">// 实例化消费者</span></span><br><span class="line">      <span class="type">DefaultMQPushConsumer</span> <span class="variable">consumer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQPushConsumer</span>(<span class="string">&quot;ExampleConsumer&quot;</span>);</span><br><span class="line">      <span class="comment">// 订阅Topics</span></span><br><span class="line">      consumer.subscribe(<span class="string">&quot;TestTopic&quot;</span>, <span class="string">&quot;*&quot;</span>);</span><br><span class="line">      <span class="comment">// 注册消息监听者</span></span><br><span class="line">      consumer.registerMessageListener(<span class="keyword">new</span> <span class="title class_">MessageListenerConcurrently</span>() &#123;</span><br><span class="line">          <span class="meta">@Override</span></span><br><span class="line">          <span class="keyword">public</span> ConsumeConcurrentlyStatus <span class="title function_">consumeMessage</span><span class="params">(List&lt;MessageExt&gt; messages, ConsumeConcurrentlyContext context)</span> &#123;</span><br><span class="line">              <span class="keyword">for</span> (MessageExt message : messages) &#123;</span><br><span class="line">                  <span class="comment">// Print approximate delay time period</span></span><br><span class="line">                  System.out.println(<span class="string">&quot;Receive message[msgId=&quot;</span> + message.getMsgId() + <span class="string">&quot;] &quot;</span> + (System.currentTimeMillis() - message.getStoreTimestamp()) + <span class="string">&quot;ms later&quot;</span>);</span><br><span class="line">              &#125;</span><br><span class="line">              <span class="keyword">return</span> ConsumeConcurrentlyStatus.CONSUME_SUCCESS;</span><br><span class="line">          &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">      <span class="comment">// 启动消费者</span></span><br><span class="line">      consumer.start();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="3-2-发送延时消息"><a href="#3-2-发送延时消息" class="headerlink" title="3.2 发送延时消息"></a>3.2 发送延时消息</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.producer.DefaultMQProducer;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.message.Message;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ScheduledMessageProducer</span> &#123;</span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">      <span class="comment">// 实例化一个生产者来产生延时消息</span></span><br><span class="line">      <span class="type">DefaultMQProducer</span> <span class="variable">producer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQProducer</span>(<span class="string">&quot;ExampleProducerGroup&quot;</span>);</span><br><span class="line">      <span class="comment">// 启动生产者</span></span><br><span class="line">      producer.start();</span><br><span class="line">      <span class="type">int</span> <span class="variable">totalMessagesToSend</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; totalMessagesToSend; i++) &#123;</span><br><span class="line">          <span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Message</span>(<span class="string">&quot;TestTopic&quot;</span>, (<span class="string">&quot;Hello scheduled message &quot;</span> + i).getBytes());</span><br><span class="line">          <span class="comment">// 设置延时等级3,这个消息将在10s之后发送(现在只支持固定的几个时间,详看delayTimeLevel)</span></span><br><span class="line">          message.setDelayTimeLevel(<span class="number">3</span>);</span><br><span class="line">          <span class="comment">// 发送消息</span></span><br><span class="line">          producer.send(message);</span><br><span class="line">      &#125;</span><br><span class="line">       <span class="comment">// 关闭生产者</span></span><br><span class="line">      producer.shutdown();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-验证"><a href="#3-3-验证" class="headerlink" title="3.3 验证"></a>3.3 验证</h3><p>您将会看到消息的消费比存储时间晚10秒。</p><h3 id="3-4-延时消息的使用场景"><a href="#3-4-延时消息的使用场景" class="headerlink" title="3.4 延时消息的使用场景"></a>3.4 延时消息的使用场景</h3><p>比如电商里，提交了一个订单就可以发送一个延时消息，1h后去检查这个订单的状态，如果还是未付款就取消订单释放库存。</p><h3 id="3-5-延时消息的使用限制"><a href="#3-5-延时消息的使用限制" class="headerlink" title="3.5 延时消息的使用限制"></a>3.5 延时消息的使用限制</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// org/apache/rocketmq/store/config/MessageStoreConfig.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="type">String</span> <span class="variable">messageDelayLevel</span> <span class="operator">=</span> <span class="string">&quot;1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h&quot;</span>;</span><br></pre></td></tr></table></figure><p>现在RocketMq并不支持任意时间的延时，需要设置几个固定的延时等级，从1s到2h分别对应着等级1到18<br>消息消费失败会进入延时消息队列，消息发送时间与设置的延时等级和重试次数有关，详见代码<code>SendMessageProcessor.java</code></p><h2 id="4-批量消息样例"><a href="#4-批量消息样例" class="headerlink" title="4 批量消息样例"></a>4 批量消息样例</h2><p>批量发送消息能显著提高传递小消息的性能。限制是这些批量消息应该有相同的topic，相同的waitStoreMsgOK，而且不能是延时消息。此外，这一批消息的总大小不应超过4MB。</p><h3 id="4-1-发送批量消息"><a href="#4-1-发送批量消息" class="headerlink" title="4.1 发送批量消息"></a>4.1 发送批量消息</h3><p>如果您每次只发送不超过4MB的消息，则很容易使用批处理，样例如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">topic</span> <span class="operator">=</span> <span class="string">&quot;BatchTest&quot;</span>;</span><br><span class="line">List&lt;Message&gt; messages = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">messages.add(<span class="keyword">new</span> <span class="title class_">Message</span>(topic, <span class="string">&quot;TagA&quot;</span>, <span class="string">&quot;OrderID001&quot;</span>, <span class="string">&quot;Hello world 0&quot;</span>.getBytes()));</span><br><span class="line">messages.add(<span class="keyword">new</span> <span class="title class_">Message</span>(topic, <span class="string">&quot;TagA&quot;</span>, <span class="string">&quot;OrderID002&quot;</span>, <span class="string">&quot;Hello world 1&quot;</span>.getBytes()));</span><br><span class="line">messages.add(<span class="keyword">new</span> <span class="title class_">Message</span>(topic, <span class="string">&quot;TagA&quot;</span>, <span class="string">&quot;OrderID003&quot;</span>, <span class="string">&quot;Hello world 2&quot;</span>.getBytes()));</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">   producer.send(messages);</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">   e.printStackTrace();</span><br><span class="line">   <span class="comment">//处理error</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="4-2-消息列表分割"><a href="#4-2-消息列表分割" class="headerlink" title="4.2 消息列表分割"></a>4.2 消息列表分割</h3><p>复杂度只有当你发送大批量时才会增长，你可能不确定它是否超过了大小限制（4MB）。这时候你最好把你的消息列表分割一下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ListSplitter</span> <span class="keyword">implements</span> <span class="title class_">Iterator</span>&lt;List&lt;Message&gt;&gt; &#123;</span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SIZE_LIMIT</span> <span class="operator">=</span> <span class="number">1024</span> * <span class="number">1024</span> * <span class="number">4</span>;</span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">final</span> List&lt;Message&gt; messages;</span><br><span class="line">   <span class="keyword">private</span> <span class="type">int</span> currIndex;</span><br><span class="line">   <span class="keyword">public</span> <span class="title function_">ListSplitter</span><span class="params">(List&lt;Message&gt; messages)</span> &#123;</span><br><span class="line">           <span class="built_in">this</span>.messages = messages;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="meta">@Override</span> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">hasNext</span><span class="params">()</span> &#123;</span><br><span class="line">       <span class="keyword">return</span> currIndex &lt; messages.size();</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="meta">@Override</span> <span class="keyword">public</span> List&lt;Message&gt; <span class="title function_">next</span><span class="params">()</span> &#123;</span><br><span class="line">       <span class="type">int</span> <span class="variable">nextIndex</span> <span class="operator">=</span> currIndex;</span><br><span class="line">       <span class="type">int</span> <span class="variable">totalSize</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">       <span class="keyword">for</span> (; nextIndex &lt; messages.size(); nextIndex++) &#123;</span><br><span class="line">           <span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> messages.get(nextIndex);</span><br><span class="line">           <span class="type">int</span> <span class="variable">tmpSize</span> <span class="operator">=</span> message.getTopic().length() + message.getBody().length;</span><br><span class="line">           Map&lt;String, String&gt; properties = message.getProperties();</span><br><span class="line">           <span class="keyword">for</span> (Map.Entry&lt;String, String&gt; entry : properties.entrySet()) &#123;</span><br><span class="line">               tmpSize += entry.getKey().length() + entry.getValue().length();</span><br><span class="line">           &#125;</span><br><span class="line">           tmpSize = tmpSize + <span class="number">20</span>; <span class="comment">// 增加日志的开销20字节</span></span><br><span class="line">           <span class="keyword">if</span> (tmpSize &gt; SIZE_LIMIT) &#123;</span><br><span class="line">               <span class="comment">//单个消息超过了最大的限制</span></span><br><span class="line">               <span class="comment">//忽略,否则会阻塞分裂的进程</span></span><br><span class="line">               <span class="keyword">if</span> (nextIndex - currIndex == <span class="number">0</span>) &#123;</span><br><span class="line">                  <span class="comment">//假如下一个子列表没有元素,则添加这个子列表然后退出循环,否则只是退出循环</span></span><br><span class="line">                  nextIndex++;</span><br><span class="line">               &#125;</span><br><span class="line">               <span class="keyword">break</span>;</span><br><span class="line">           &#125;</span><br><span class="line">           <span class="keyword">if</span> (tmpSize + totalSize &gt; SIZE_LIMIT) &#123;</span><br><span class="line">               <span class="keyword">break</span>;</span><br><span class="line">           &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">               totalSize += tmpSize;</span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line">       &#125;</span><br><span class="line">       List&lt;Message&gt; subList = messages.subList(currIndex, nextIndex);</span><br><span class="line">       currIndex = nextIndex;</span><br><span class="line">       <span class="keyword">return</span> subList;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//把大的消息分裂成若干个小的消息</span></span><br><span class="line"><span class="type">ListSplitter</span> <span class="variable">splitter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ListSplitter</span>(messages);</span><br><span class="line"><span class="keyword">while</span> (splitter.hasNext()) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">      List&lt;Message&gt;  listItem = splitter.next();</span><br><span class="line">      producer.send(listItem);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">      e.printStackTrace();</span><br><span class="line">      <span class="comment">//处理error</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="5-过滤消息样例"><a href="#5-过滤消息样例" class="headerlink" title="5 过滤消息样例"></a>5 过滤消息样例</h2><p>在大多数情况下，TAG是一个简单而有用的设计，其可以来选择您想要的消息。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">DefaultMQPushConsumer</span> <span class="variable">consumer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQPushConsumer</span>(<span class="string">&quot;CID_EXAMPLE&quot;</span>);</span><br><span class="line">consumer.subscribe(<span class="string">&quot;TOPIC&quot;</span>, <span class="string">&quot;TAGA || TAGB || TAGC&quot;</span>);</span><br></pre></td></tr></table></figure><p>消费者将接收包含TAGA或TAGB或TAGC的消息。但是限制是一个消息只能有一个标签，这对于复杂的场景可能不起作用。在这种情况下，可以使用SQL表达式筛选消息。SQL特性可以通过发送消息时的属性来进行计算。在RocketMQ定义的语法下，可以实现一些简单的逻辑。下面是一个例子：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">------------</span><br><span class="line">| message  |</span><br><span class="line">|----------|  a &gt; 5 AND b = &#x27;abc&#x27;</span><br><span class="line">| a = 10   |  --------------------&gt; Gotten</span><br><span class="line">| b = &#x27;abc&#x27;|</span><br><span class="line">| c = true |</span><br><span class="line">------------</span><br><span class="line">------------</span><br><span class="line">| message  |</span><br><span class="line">|----------|   a &gt; 5 AND b = &#x27;abc&#x27;</span><br><span class="line">| a = 1    |  --------------------&gt; Missed</span><br><span class="line">| b = &#x27;abc&#x27;|</span><br><span class="line">| c = true |</span><br><span class="line">------------</span><br></pre></td></tr></table></figure><h3 id="5-1-基本语法"><a href="#5-1-基本语法" class="headerlink" title="5.1 基本语法"></a>5.1 基本语法</h3><p>RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。</p><ul><li>数值比较，比如：**&gt;，&gt;&#x3D;，&lt;，&lt;&#x3D;，BETWEEN，&#x3D;；**</li><li>字符比较，比如：**&#x3D;，&lt;&gt;，IN；**</li><li><strong>IS NULL</strong> 或者 <strong>IS NOT NULL；</strong></li><li>逻辑符号 <strong>AND，OR，NOT；</strong></li></ul><p>常量支持类型为：</p><ul><li>数值，比如：<strong>123，3.1415；</strong></li><li>字符，比如：**’abc’，必须用单引号包裹起来；**</li><li><strong>NULL</strong>，特殊的常量</li><li>布尔值，<strong>TRUE</strong> 或 <strong>FALSE</strong></li></ul><p>只有使用push模式的消费者才能用使用SQL92标准的sql语句，接口如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">public void subscribe(finalString topic, final MessageSelector messageSelector)</span><br></pre></td></tr></table></figure><h3 id="5-2-使用样例"><a href="#5-2-使用样例" class="headerlink" title="5.2 使用样例"></a>5.2 使用样例</h3><h4 id="1、生产者样例"><a href="#1、生产者样例" class="headerlink" title="1、生产者样例"></a>1、生产者样例</h4><p>发送消息时，你能通过<code>putUserProperty</code>来设置消息的属性</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">DefaultMQProducer</span> <span class="variable">producer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQProducer</span>(<span class="string">&quot;please_rename_unique_group_name&quot;</span>);</span><br><span class="line">producer.start();</span><br><span class="line"><span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Message</span>(<span class="string">&quot;TopicTest&quot;</span>,</span><br><span class="line">   tag,</span><br><span class="line">   (<span class="string">&quot;Hello RocketMQ &quot;</span> + i).getBytes(RemotingHelper.DEFAULT_CHARSET)</span><br><span class="line">);</span><br><span class="line"><span class="comment">// 设置一些属性</span></span><br><span class="line">msg.putUserProperty(<span class="string">&quot;a&quot;</span>, String.valueOf(i));</span><br><span class="line"><span class="type">SendResult</span> <span class="variable">sendResult</span> <span class="operator">=</span> producer.send(msg);</span><br><span class="line"></span><br><span class="line">producer.shutdown();</span><br></pre></td></tr></table></figure><h4 id="2、消费者样例"><a href="#2、消费者样例" class="headerlink" title="2、消费者样例"></a>2、消费者样例</h4><p>用MessageSelector.bySql来使用sql筛选消息</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">DefaultMQPushConsumer</span> <span class="variable">consumer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQPushConsumer</span>(<span class="string">&quot;please_rename_unique_group_name_4&quot;</span>);</span><br><span class="line"><span class="comment">// 只有订阅的消息有这个属性a, a &gt;=0 and a &lt;= 3</span></span><br><span class="line">consumer.subscribe(<span class="string">&quot;TopicTest&quot;</span>, MessageSelector.bySql(<span class="string">&quot;a between 0 and 3&quot;</span>);</span><br><span class="line">consumer.registerMessageListener(<span class="keyword">new</span> <span class="title class_">MessageListenerConcurrently</span>() &#123;</span><br><span class="line">   <span class="meta">@Override</span></span><br><span class="line">   <span class="keyword">public</span> ConsumeConcurrentlyStatus <span class="title function_">consumeMessage</span><span class="params">(List&lt;MessageExt&gt; msgs, ConsumeConcurrentlyContext context)</span> &#123;</span><br><span class="line">       <span class="keyword">return</span> ConsumeConcurrentlyStatus.CONSUME_SUCCESS;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;);</span><br><span class="line">consumer.start();</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="6-消息事务样例"><a href="#6-消息事务样例" class="headerlink" title="6 消息事务样例"></a>6 消息事务样例</h2><p>事务消息共有三种状态，提交状态、回滚状态、中间状态：</p><ul><li>TransactionStatus.CommitTransaction: 提交事务，它允许消费者消费此消息。</li><li>TransactionStatus.RollbackTransaction: 回滚事务，它代表该消息将被删除，不允许被消费。</li><li>TransactionStatus.Unknown: 中间状态，它代表需要检查消息队列来确定状态。</li></ul><h3 id="6-1-发送事务消息样例"><a href="#6-1-发送事务消息样例" class="headerlink" title="6.1 发送事务消息样例"></a>6.1 发送事务消息样例</h3><h4 id="1、创建事务性生产者"><a href="#1、创建事务性生产者" class="headerlink" title="1、创建事务性生产者"></a>1、创建事务性生产者</h4><p>使用 <code>TransactionMQProducer</code>类创建生产者，并指定唯一的 <code>ProducerGroup</code>，就可以设置自定义线程池来处理这些检查请求。执行本地事务后、需要根据执行结果对消息队列进行回复。回传的事务状态在请参考前一节。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.message.MessageExt;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TransactionProducer</span> &#123;</span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> MQClientException, InterruptedException &#123;</span><br><span class="line">       <span class="type">TransactionListener</span> <span class="variable">transactionListener</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TransactionListenerImpl</span>();</span><br><span class="line">       <span class="type">TransactionMQProducer</span> <span class="variable">producer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TransactionMQProducer</span>(<span class="string">&quot;please_rename_unique_group_name&quot;</span>);</span><br><span class="line">       <span class="type">ExecutorService</span> <span class="variable">executorService</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(<span class="number">2</span>, <span class="number">5</span>, <span class="number">100</span>, TimeUnit.SECONDS, <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;Runnable&gt;(<span class="number">2000</span>), <span class="keyword">new</span> <span class="title class_">ThreadFactory</span>() &#123;</span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="keyword">public</span> Thread <span class="title function_">newThread</span><span class="params">(Runnable r)</span> &#123;</span><br><span class="line">               <span class="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(r);</span><br><span class="line">               thread.setName(<span class="string">&quot;client-transaction-msg-check-thread&quot;</span>);</span><br><span class="line">               <span class="keyword">return</span> thread;</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;);</span><br><span class="line">       producer.setExecutorService(executorService);</span><br><span class="line">       producer.setTransactionListener(transactionListener);</span><br><span class="line">       producer.start();</span><br><span class="line">       String[] tags = <span class="keyword">new</span> <span class="title class_">String</span>[] &#123;<span class="string">&quot;TagA&quot;</span>, <span class="string">&quot;TagB&quot;</span>, <span class="string">&quot;TagC&quot;</span>, <span class="string">&quot;TagD&quot;</span>, <span class="string">&quot;TagE&quot;</span>&#125;;</span><br><span class="line">       <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">           <span class="keyword">try</span> &#123;</span><br><span class="line">               <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span></span><br><span class="line">                   <span class="keyword">new</span> <span class="title class_">Message</span>(<span class="string">&quot;TopicTest1234&quot;</span>, tags[i % tags.length], <span class="string">&quot;KEY&quot;</span> + i,</span><br><span class="line">                       (<span class="string">&quot;Hello RocketMQ &quot;</span> + i).getBytes(RemotingHelper.DEFAULT_CHARSET));</span><br><span class="line">               <span class="type">SendResult</span> <span class="variable">sendResult</span> <span class="operator">=</span> producer.sendMessageInTransaction(msg, <span class="literal">null</span>);</span><br><span class="line">               System.out.printf(<span class="string">&quot;%s%n&quot;</span>, sendResult);</span><br><span class="line">               Thread.sleep(<span class="number">10</span>);</span><br><span class="line">           &#125; <span class="keyword">catch</span> (MQClientException | UnsupportedEncodingException e) &#123;</span><br><span class="line">               e.printStackTrace();</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line">       <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100000</span>; i++) &#123;</span><br><span class="line">           Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">       &#125;</span><br><span class="line">       producer.shutdown();</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="2、实现事务的监听接口"><a href="#2、实现事务的监听接口" class="headerlink" title="2、实现事务的监听接口"></a>2、实现事务的监听接口</h4><p>当发送半消息成功时，我们使用 <code>executeLocalTransaction</code> 方法来执行本地事务。它返回前一节中提到的三个事务状态之一。<code>checkLocalTranscation</code> 方法用于检查本地事务状态，并回应消息队列的检查请求。它也是返回前一节中提到的三个事务状态之一。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TransactionListenerImpl</span> <span class="keyword">implements</span> <span class="title class_">TransactionListener</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="type">AtomicInteger</span> <span class="variable">transactionIndex</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="keyword">private</span> ConcurrentHashMap&lt;String, Integer&gt; localTrans = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> LocalTransactionState <span class="title function_">executeLocalTransaction</span><span class="params">(Message msg, Object arg)</span> &#123;</span><br><span class="line">      <span class="type">int</span> <span class="variable">value</span> <span class="operator">=</span> transactionIndex.getAndIncrement();</span><br><span class="line">      <span class="type">int</span> <span class="variable">status</span> <span class="operator">=</span> value % <span class="number">3</span>;</span><br><span class="line">      localTrans.put(msg.getTransactionId(), status);</span><br><span class="line">      <span class="keyword">return</span> LocalTransactionState.UNKNOW;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> LocalTransactionState <span class="title function_">checkLocalTransaction</span><span class="params">(MessageExt msg)</span> &#123;</span><br><span class="line">      <span class="type">Integer</span> <span class="variable">status</span> <span class="operator">=</span> localTrans.get(msg.getTransactionId());</span><br><span class="line">      <span class="keyword">if</span> (<span class="literal">null</span> != status) &#123;</span><br><span class="line">          <span class="keyword">switch</span> (status) &#123;</span><br><span class="line">              <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">                  <span class="keyword">return</span> LocalTransactionState.UNKNOW;</span><br><span class="line">              <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">                  <span class="keyword">return</span> LocalTransactionState.COMMIT_MESSAGE;</span><br><span class="line">              <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">                  <span class="keyword">return</span> LocalTransactionState.ROLLBACK_MESSAGE;</span><br><span class="line">          &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> LocalTransactionState.COMMIT_MESSAGE;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="6-2-事务消息使用上的限制"><a href="#6-2-事务消息使用上的限制" class="headerlink" title="6.2 事务消息使用上的限制"></a>6.2 事务消息使用上的限制</h3><ol><li>事务消息不支持延时消息和批量消息。</li><li>为了避免单个消息被检查太多次而导致半队列消息累积，我们默认将单个消息的检查次数限制为 15 次，但是用户可以通过 Broker 配置文件的 <code>transactionCheckMax</code>参数来修改此限制。如果已经检查某条消息超过 N 次的话（ N &#x3D; <code>transactionCheckMax</code> ） 则 Broker 将丢弃此消息，并在默认情况下同时打印错误日志。用户可以通过重写 <code>AbstractTransactionCheckListener</code> 类来修改这个行为。</li><li>事务消息将在 Broker 配置文件中的参数 transactionMsgTimeout 这样的特定时间长度之后被检查。当发送事务消息时，用户还可以通过设置用户属性 CHECK_IMMUNITY_TIME_IN_SECONDS 来改变这个限制，该参数优先于 <code>transactionMsgTimeout</code> 参数。</li><li>事务性消息可能不止一次被检查或消费。</li><li>提交给用户的目标主题消息可能会失败，目前这依日志的记录而定。它的高可用性通过 RocketMQ 本身的高可用性机制来保证，如果希望确保事务消息不丢失、并且事务完整性得到保证，建议使用同步的双重写入机制。</li><li>事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。与其他类型的消息不同，事务消息允许反向查询、MQ服务器能通过它们的生产者 ID 查询到消费者。</li></ol><h2 id="7-Logappender样例"><a href="#7-Logappender样例" class="headerlink" title="7 Logappender样例"></a>7 Logappender样例</h2><p>RocketMQ日志提供log4j、log4j2和logback日志框架作为业务应用，下面是配置样例</p><h3 id="7-1-log4j样例"><a href="#7-1-log4j样例" class="headerlink" title="7.1 log4j样例"></a>7.1 log4j样例</h3><p>按下面样例使用log4j属性配置</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">log4j.appender.mq=org.apache.rocketmq.logappender.log4j.RocketmqLog4jAppender</span><br><span class="line">log4j.appender.mq.Tag=yourTag</span><br><span class="line">log4j.appender.mq.Topic=yourLogTopic</span><br><span class="line">log4j.appender.mq.ProducerGroup=yourLogGroup</span><br><span class="line">log4j.appender.mq.NameServerAddress=yourRocketmqNameserverAddress</span><br><span class="line">log4j.appender.mq.layout=org.apache.log4j.PatternLayout</span><br><span class="line">log4j.appender.mq.layout.ConversionPattern=%d&#123;yyyy-MM-dd HH:mm:ss&#125; %-4r [%t] (%F:%L) %-5p - %m%n</span><br></pre></td></tr></table></figure><p>按下面样例使用log4j xml配置来使用异步添加日志</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">&lt;appender name=&quot;mqAppender1&quot;class=&quot;org.apache.rocketmq.logappender.log4j.RocketmqLog4jAppender&quot;&gt;</span><br><span class="line">  &lt;param name=&quot;Tag&quot; value=&quot;yourTag&quot; /&gt;</span><br><span class="line">  &lt;param name=&quot;Topic&quot; value=&quot;yourLogTopic&quot; /&gt;</span><br><span class="line">  &lt;param name=&quot;ProducerGroup&quot; value=&quot;yourLogGroup&quot; /&gt;</span><br><span class="line">  &lt;param name=&quot;NameServerAddress&quot; value=&quot;yourRocketmqNameserverAddress&quot;/&gt;</span><br><span class="line">  &lt;layout class=&quot;org.apache.log4j.PatternLayout&quot;&gt;</span><br><span class="line">      &lt;param name=&quot;ConversionPattern&quot; value=&quot;%d&#123;yyyy-MM-dd HH:mm:ss&#125;-%p %t %c - %m%n&quot; /&gt;</span><br><span class="line">  &lt;/layout&gt;</span><br><span class="line">&lt;/appender&gt;</span><br><span class="line">&lt;appender name=&quot;mqAsyncAppender1&quot;class=&quot;org.apache.log4j.AsyncAppender&quot;&gt;</span><br><span class="line">  &lt;param name=&quot;BufferSize&quot; value=&quot;1024&quot; /&gt;</span><br><span class="line">  &lt;param name=&quot;Blocking&quot; value=&quot;false&quot; /&gt;</span><br><span class="line">  &lt;appender-ref ref=&quot;mqAppender1&quot;/&gt;</span><br><span class="line">&lt;/appender&gt;</span><br></pre></td></tr></table></figure><h3 id="7-2-log4j2样例"><a href="#7-2-log4j2样例" class="headerlink" title="7.2 log4j2样例"></a>7.2 log4j2样例</h3><p>用log4j2时，配置如下，如果想要非阻塞，只需要使用异步添加引用即可</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;RocketMQ name=&quot;rocketmqAppender&quot; producerGroup=&quot;yourLogGroup&quot; nameServerAddress=&quot;yourRocketmqNameserverAddress&quot;</span><br><span class="line">   topic=&quot;yourLogTopic&quot; tag=&quot;yourTag&quot;&gt;</span><br><span class="line">  &lt;PatternLayout pattern=&quot;%d [%p] hahahah %c %m%n&quot;/&gt;</span><br><span class="line">&lt;/RocketMQ&gt;</span><br></pre></td></tr></table></figure><h3 id="7-3-logback样例"><a href="#7-3-logback样例" class="headerlink" title="7.3 logback样例"></a>7.3 logback样例</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">&lt;appender name=&quot;mqAppender1&quot;class=&quot;org.apache.rocketmq.logappender.logback.RocketmqLogbackAppender&quot;&gt;</span><br><span class="line">  &lt;tag&gt;yourTag&lt;/tag&gt;</span><br><span class="line">  &lt;topic&gt;yourLogTopic&lt;/topic&gt;</span><br><span class="line">  &lt;producerGroup&gt;yourLogGroup&lt;/producerGroup&gt;</span><br><span class="line">  &lt;nameServerAddress&gt;yourRocketmqNameserverAddress&lt;/nameServerAddress&gt;</span><br><span class="line">  &lt;layout&gt;</span><br><span class="line">      &lt;pattern&gt;%date %p %t - %m%n&lt;/pattern&gt;</span><br><span class="line">  &lt;/layout&gt;</span><br><span class="line">&lt;/appender&gt;</span><br><span class="line">&lt;appender name=&quot;mqAsyncAppender1&quot;class=&quot;ch.qos.logback.classic.AsyncAppender&quot;&gt;</span><br><span class="line">  &lt;queueSize&gt;1024&lt;/queueSize&gt;</span><br><span class="line">  &lt;discardingThreshold&gt;80&lt;/discardingThreshold&gt;</span><br><span class="line">  &lt;maxFlushTime&gt;2000&lt;/maxFlushTime&gt;</span><br><span class="line">  &lt;neverBlock&gt;true&lt;/neverBlock&gt;</span><br><span class="line">  &lt;appender-ref ref=&quot;mqAppender1&quot;/&gt;</span><br><span class="line">&lt;/appender&gt;</span><br></pre></td></tr></table></figure><h2 id="8-OpenMessaging样例"><a href="#8-OpenMessaging样例" class="headerlink" title="8 OpenMessaging样例"></a>8 OpenMessaging样例</h2><p> <a href="https://www.google.com/url?q=http://openmessaging.cloud/&sa=D&ust=1546524111089000">OpenMessaging</a>旨在建立消息和流处理规范，以为金融、电子商务、物联网和大数据领域提供通用框架及工业级指导方案。在分布式异构环境中，设计原则是面向云、简单、灵活和独立于语言。符合这些规范将帮助企业方便的开发跨平台和操作系统的异构消息传递应用程序。提供了openmessaging-api 0.3.0-alpha的部分实现，下面的示例演示如何基于OpenMessaging访问RocketMQ。</p><h3 id="8-1-OMSProducer样例"><a href="#8-1-OMSProducer样例" class="headerlink" title="8.1 OMSProducer样例"></a>8.1 OMSProducer样例</h3><p>下面的示例演示如何在同步、异步或单向传输中向RocketMQ代理发送消息。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> io.openmessaging.Future;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.FutureListener;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.Message;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.MessagingAccessPoint;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.OMS;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.producer.Producer;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.producer.SendResult;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.Charset;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.CountDownLatch;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SimpleProducer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">       <span class="keyword">final</span> <span class="type">MessagingAccessPoint</span> <span class="variable">messagingAccessPoint</span> <span class="operator">=</span></span><br><span class="line">           OMS.getMessagingAccessPoint(<span class="string">&quot;oms:rocketmq://localhost:9876/default:default&quot;</span>);</span><br><span class="line">       <span class="keyword">final</span> <span class="type">Producer</span> <span class="variable">producer</span> <span class="operator">=</span> messagingAccessPoint.createProducer();</span><br><span class="line">       messagingAccessPoint.startup();</span><br><span class="line">       System.out.printf(<span class="string">&quot;MessagingAccessPoint startup OK%n&quot;</span>);</span><br><span class="line">       producer.startup();</span><br><span class="line">       System.out.printf(<span class="string">&quot;Producer startup OK%n&quot;</span>);</span><br><span class="line">       &#123;</span><br><span class="line">           <span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> producer.createBytesMessage(<span class="string">&quot;OMS_HELLO_TOPIC&quot;</span>, <span class="string">&quot;OMS_HELLO_BODY&quot;</span>.getBytes(Charset.forName(<span class="string">&quot;UTF-8&quot;</span>)));</span><br><span class="line">           <span class="type">SendResult</span> <span class="variable">sendResult</span> <span class="operator">=</span> producer.send(message);</span><br><span class="line">           <span class="comment">//final Void aVoid = result.get(3000L);</span></span><br><span class="line">           System.out.printf(<span class="string">&quot;Send async message OK, msgId: %s%n&quot;</span>, sendResult.messageId());</span><br><span class="line">       &#125;</span><br><span class="line">       <span class="keyword">final</span> <span class="type">CountDownLatch</span> <span class="variable">countDownLatch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(<span class="number">1</span>);</span><br><span class="line">       &#123;</span><br><span class="line">           <span class="keyword">final</span> Future&lt;SendResult&gt; result = producer.sendAsync(producer.createBytesMessage(<span class="string">&quot;OMS_HELLO_TOPIC&quot;</span>, <span class="string">&quot;OMS_HELLO_BODY&quot;</span>.getBytes(Charset.forName(<span class="string">&quot;UTF-8&quot;</span>))));</span><br><span class="line">           result.addListener(<span class="keyword">new</span> <span class="title class_">FutureListener</span>&lt;SendResult&gt;() &#123;</span><br><span class="line">               <span class="meta">@Override</span></span><br><span class="line">               <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">operationComplete</span><span class="params">(Future&lt;SendResult&gt; future)</span> &#123;</span><br><span class="line">                   <span class="keyword">if</span> (future.getThrowable() != <span class="literal">null</span>) &#123;</span><br><span class="line">                       System.out.printf(<span class="string">&quot;Send async message Failed, error: %s%n&quot;</span>, future.getThrowable().getMessage());</span><br><span class="line">                   &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                       System.out.printf(<span class="string">&quot;Send async message OK, msgId: %s%n&quot;</span>, future.get().messageId());</span><br><span class="line">                   &#125;</span><br><span class="line">                   countDownLatch.countDown();</span><br><span class="line">               &#125;</span><br><span class="line">           &#125;);</span><br><span class="line">       &#125;</span><br><span class="line">       &#123;</span><br><span class="line">           producer.sendOneway(producer.createBytesMessage(<span class="string">&quot;OMS_HELLO_TOPIC&quot;</span>, <span class="string">&quot;OMS_HELLO_BODY&quot;</span>.getBytes(Charset.forName(<span class="string">&quot;UTF-8&quot;</span>))));</span><br><span class="line">           System.out.printf(<span class="string">&quot;Send oneway message OK%n&quot;</span>);</span><br><span class="line">       &#125;</span><br><span class="line">       <span class="keyword">try</span> &#123;</span><br><span class="line">           countDownLatch.await();</span><br><span class="line">           Thread.sleep(<span class="number">500</span>); <span class="comment">// 等一些时间来发送消息</span></span><br><span class="line">       &#125; <span class="keyword">catch</span> (InterruptedException ignore) &#123;</span><br><span class="line">       &#125;</span><br><span class="line">       producer.shutdown();</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="8-2-OMSPullConsumer"><a href="#8-2-OMSPullConsumer" class="headerlink" title="8.2 OMSPullConsumer"></a>8.2 OMSPullConsumer</h3><p>用OMS PullConsumer 来从指定的队列中拉取消息</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> io.openmessaging.Message;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.MessagingAccessPoint;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.OMS;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.OMSBuiltinKeys;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.consumer.PullConsumer;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.producer.Producer;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.producer.SendResult;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SimplePullConsumer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">       <span class="keyword">final</span> <span class="type">MessagingAccessPoint</span> <span class="variable">messagingAccessPoint</span> <span class="operator">=</span></span><br><span class="line">           OMS.getMessagingAccessPoint(<span class="string">&quot;oms:rocketmq://localhost:9876/default:default&quot;</span>);</span><br><span class="line">       messagingAccessPoint.startup();</span><br><span class="line">       <span class="keyword">final</span> <span class="type">Producer</span> <span class="variable">producer</span> <span class="operator">=</span> messagingAccessPoint.createProducer();</span><br><span class="line">       <span class="keyword">final</span> <span class="type">PullConsumer</span> <span class="variable">consumer</span> <span class="operator">=</span> messagingAccessPoint.createPullConsumer(</span><br><span class="line">           OMS.newKeyValue().put(OMSBuiltinKeys.CONSUMER_ID, <span class="string">&quot;OMS_CONSUMER&quot;</span>));</span><br><span class="line">       messagingAccessPoint.startup();</span><br><span class="line">       System.out.printf(<span class="string">&quot;MessagingAccessPoint startup OK%n&quot;</span>);</span><br><span class="line">       <span class="keyword">final</span> <span class="type">String</span> <span class="variable">queueName</span> <span class="operator">=</span> <span class="string">&quot;TopicTest&quot;</span>;</span><br><span class="line">       producer.startup();</span><br><span class="line">       <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> producer.createBytesMessage(queueName, <span class="string">&quot;Hello Open Messaging&quot;</span>.getBytes());</span><br><span class="line">       <span class="type">SendResult</span> <span class="variable">sendResult</span> <span class="operator">=</span> producer.send(msg);</span><br><span class="line">       System.out.printf(<span class="string">&quot;Send Message OK. MsgId: %s%n&quot;</span>, sendResult.messageId());</span><br><span class="line">       producer.shutdown();</span><br><span class="line">       consumer.attachQueue(queueName);</span><br><span class="line">       consumer.startup();</span><br><span class="line">       System.out.printf(<span class="string">&quot;Consumer startup OK%n&quot;</span>);</span><br><span class="line">       <span class="comment">// 运行直到发现一个消息被发送了</span></span><br><span class="line">       <span class="type">boolean</span> <span class="variable">stop</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">       <span class="keyword">while</span> (!stop) &#123;</span><br><span class="line">           <span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> consumer.receive();</span><br><span class="line">           <span class="keyword">if</span> (message != <span class="literal">null</span>) &#123;</span><br><span class="line">               <span class="type">String</span> <span class="variable">msgId</span> <span class="operator">=</span> message.sysHeaders().getString(Message.BuiltinKeys.MESSAGE_ID);</span><br><span class="line">               System.out.printf(<span class="string">&quot;Received one message: %s%n&quot;</span>, msgId);</span><br><span class="line">               consumer.ack(msgId);</span><br><span class="line">               <span class="keyword">if</span> (!stop) &#123;</span><br><span class="line">                   stop = msgId.equalsIgnoreCase(sendResult.messageId());</span><br><span class="line">               &#125;</span><br><span class="line">           &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">               System.out.printf(<span class="string">&quot;Return without any message%n&quot;</span>);</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line">       consumer.shutdown();</span><br><span class="line">       messagingAccessPoint.shutdown();</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="8-3-OMSPushConsumer"><a href="#8-3-OMSPushConsumer" class="headerlink" title="8.3 OMSPushConsumer"></a>8.3 OMSPushConsumer</h3><p>以下示范如何将 OMS PushConsumer 添加到指定的队列，并通过 MessageListener 消费这些消息。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> io.openmessaging.Message;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.MessagingAccessPoint;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.OMS;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.OMSBuiltinKeys;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.consumer.MessageListener;</span><br><span class="line"><span class="keyword">import</span> io.openmessaging.consumer.PushConsumer;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SimplePushConsumer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">       <span class="keyword">final</span> <span class="type">MessagingAccessPoint</span> <span class="variable">messagingAccessPoint</span> <span class="operator">=</span> OMS</span><br><span class="line">           .getMessagingAccessPoint(<span class="string">&quot;oms:rocketmq://localhost:9876/default:default&quot;</span>);</span><br><span class="line">       <span class="keyword">final</span> <span class="type">PushConsumer</span> <span class="variable">consumer</span> <span class="operator">=</span> messagingAccessPoint.</span><br><span class="line">           createPushConsumer(OMS.newKeyValue().put(OMSBuiltinKeys.CONSUMER_ID, <span class="string">&quot;OMS_CONSUMER&quot;</span>));</span><br><span class="line">       messagingAccessPoint.startup();</span><br><span class="line">       System.out.printf(<span class="string">&quot;MessagingAccessPoint startup OK%n&quot;</span>);</span><br><span class="line">       Runtime.getRuntime().addShutdownHook(<span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">               consumer.shutdown();</span><br><span class="line">               messagingAccessPoint.shutdown();</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;));</span><br><span class="line">       consumer.attachQueue(<span class="string">&quot;OMS_HELLO_TOPIC&quot;</span>, <span class="keyword">new</span> <span class="title class_">MessageListener</span>() &#123;</span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceived</span><span class="params">(Message message, Context context)</span> &#123;</span><br><span class="line">               System.out.printf(<span class="string">&quot;Received one message: %s%n&quot;</span>, message.sysHeaders().getString(Message.BuiltinKeys.MESSAGE_ID));</span><br><span class="line">               context.ack();</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;);</span><br><span class="line">       consumer.startup();</span><br><span class="line">       System.out.printf(<span class="string">&quot;Consumer startup OK%n&quot;</span>);</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;样例&quot;&gt;&lt;a href=&quot;#样例&quot; class=&quot;headerlink&quot; title=&quot;样例&quot;&gt;&lt;/a&gt;样例&lt;/h1&gt;&lt;hr&gt;
&lt;h2 id=&quot;1-基本样例&quot;&gt;&lt;a href=&quot;#1-基本样例&quot; class=&quot;headerlink&quot; title=&quot;1 基本样例&quot;&gt;</summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="样例" scheme="https://nydia.github.io/tags/%E6%A0%B7%E4%BE%8B/"/>
    
  </entry>
  
  <entry>
    <title>Apache RocketMQ开发者指南</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/cn/README/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/cn/README/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T12:57:58.558Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Apache-RocketMQ开发者指南"><a href="#Apache-RocketMQ开发者指南" class="headerlink" title="Apache RocketMQ开发者指南"></a>Apache RocketMQ开发者指南</h2><h5 id="这个开发者指南是帮忙您快速了解-并使用-Apache-RocketMQ"><a href="#这个开发者指南是帮忙您快速了解-并使用-Apache-RocketMQ" class="headerlink" title="这个开发者指南是帮忙您快速了解,并使用 Apache RocketMQ"></a>这个开发者指南是帮忙您快速了解,并使用 Apache RocketMQ</h5><h3 id="1-概念和特性"><a href="#1-概念和特性" class="headerlink" title="1. 概念和特性"></a>1. 概念和特性</h3><ul><li><p><a href="concept.md">概念(Concept)</a>：介绍RocketMQ的基本概念模型。</p></li><li><p><a href="features.md">特性(Features)</a>：介绍RocketMQ实现的功能特性。</p></li></ul><h3 id="2-架构设计"><a href="#2-架构设计" class="headerlink" title="2. 架构设计"></a>2. 架构设计</h3><ul><li><p><a href="architecture.md">架构(Architecture)</a>：介绍RocketMQ部署架构和技术架构。</p></li><li><p><a href="design.md">设计(Design)</a>：介绍RocketMQ关键机制的设计原理，主要包括消息存储、通信机制、消息过滤、负载均衡、事务消息等。</p></li></ul><h3 id="3-样例"><a href="#3-样例" class="headerlink" title="3. 样例"></a>3. 样例</h3><ul><li><a href="RocketMQ_Example.md">样例(Example)</a> ：介绍RocketMQ的常见用法，包括基本样例、顺序消息样例、延时消息样例、批量消息样例、过滤消息样例、事物消息样例等。</li></ul><h3 id="4-最佳实践"><a href="#4-最佳实践" class="headerlink" title="4. 最佳实践"></a>4. 最佳实践</h3><ul><li><p><a href="best_practice.md">最佳实践（Best Practice）</a>：介绍RocketMQ的最佳实践，包括生产者、消费者、Broker以及NameServer的最佳实践，客户端的配置方式以及JVM和linux的最佳参数配置。</p></li><li><p><a href="msg_trace/user_guide.md">消息轨迹指南(Message Trace)</a>：介绍RocketMQ消息轨迹的使用方法。</p></li><li><p><a href="acl/user_guide.md">权限管理(Auth Management)</a>：介绍如何快速部署和使用支持权限控制特性的RocketMQ集群。</p></li><li><p><a href="dledger/quick_start.md">Dledger快速搭建(Quick Start)</a>：介绍Dledger的快速搭建方法。</p></li><li><p><a href="dledger/deploy_guide.md">集群部署(Cluster Deployment)</a>：介绍Dledger的集群部署方式。</p></li></ul><h3 id="5-运维管理"><a href="#5-运维管理" class="headerlink" title="5. 运维管理"></a>5. 运维管理</h3><ul><li><a href="operation.md">集群部署(Operation)</a>：介绍单Master模式、多Master模式、多Master多slave模式等RocketMQ集群各种形式的部署方法以及运维工具mqadmin的使用方式。</li></ul><h3 id="6-API-Reference（待补充）"><a href="#6-API-Reference（待补充）" class="headerlink" title="6. API Reference（待补充）"></a>6. API Reference（待补充）</h3><ul><li><a href="client/java/API_Reference_DefaultMQProducer.md">DefaultMQProducer API Reference</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Apache-RocketMQ开发者指南&quot;&gt;&lt;a href=&quot;#Apache-RocketMQ开发者指南&quot; class=&quot;headerlink&quot; title=&quot;Apache RocketMQ开发者指南&quot;&gt;&lt;/a&gt;Apache RocketMQ开发者指南&lt;/h2&gt;&lt;</summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="Apache RocketMQ开发者指南" scheme="https://nydia.github.io/tags/Apache-RocketMQ%E5%BC%80%E5%8F%91%E8%80%85%E6%8C%87%E5%8D%97/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ架构设计</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/cn/architecture/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/cn/architecture/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T12:56:22.986Z</updated>
    
    <content type="html"><![CDATA[<h1 id="架构设计"><a href="#架构设计" class="headerlink" title="架构设计"></a>架构设计</h1><hr><h2 id="1-技术架构"><a href="#1-技术架构" class="headerlink" title="1 技术架构"></a>1 技术架构</h2><p><img src="/image/rocketmq_architecture_1.png"></p><p>RocketMQ架构上主要分为四部分，如上图所示:</p><ul><li><p>Producer：消息发布的角色，支持分布式集群方式部署。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递，投递的过程支持快速失败并且低延迟。</p></li><li><p>Consumer：消息消费的角色，支持分布式集群方式部署。支持以push推，pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费，它提供实时消息订阅机制，可以满足大多数用户的需求。</p></li><li><p>NameServer：NameServer是一个非常简单的Topic路由注册中心，其角色类似Dubbo中的zookeeper，支持Broker的动态注册与发现。主要包括两个功能：Broker管理，NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制，检查Broker是否还存活；路由信息管理，每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息，从而进行消息的投递和消费。NameServer通常也是集群的方式部署，各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息，所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了，Broker仍然可以向其它NameServer同步其路由信息，Producer,Consumer仍然可以动态感知Broker的路由的信息。 </p></li><li><p>BrokerServer：Broker主要负责消息的存储、投递和查询以及服务高可用保证，为了实现这些功能，Broker包含了以下几个重要子模块。</p></li></ul><ol><li>Remoting Module：整个Broker的实体，负责处理来自clients端的请求。</li><li>Client Manager：负责管理客户端(Producer&#x2F;Consumer)和维护Consumer的Topic订阅信息</li><li>Store Service：提供方便简单的API接口处理消息存储到物理硬盘和查询功能。</li><li>HA Service：高可用服务，提供Master Broker 和 Slave Broker之间的数据同步功能。</li><li>Index Service：根据特定的Message key对投递到Broker的消息进行索引服务，以提供消息的快速查询。</li></ol><p><img src="/image/rocketmq_architecture_2.png"></p><h2 id="2-部署架构"><a href="#2-部署架构" class="headerlink" title="2 部署架构"></a>2 部署架构</h2><p><img src="/image/rocketmq_architecture_3.png"></p><h3 id="RocketMQ-网络部署特点"><a href="#RocketMQ-网络部署特点" class="headerlink" title="RocketMQ 网络部署特点"></a>RocketMQ 网络部署特点</h3><ul><li><p>NameServer是一个几乎无状态节点，可集群部署，节点之间无任何信息同步。</p></li><li><p>Broker部署相对复杂，Broker分为Master与Slave，一个Master可以对应多个Slave，但是一个Slave只能对应一个Master，Master与Slave 的对应关系通过指定相同的BrokerName，不同的BrokerId 来定义，BrokerId为0表示Master，非0表示Slave。Master也可以部署多个。每个Broker与NameServer集群中的所有节点建立长连接，定时注册Topic信息到所有NameServer。 注意：当前RocketMQ版本在部署架构上支持一Master多Slave，但只有BrokerId&#x3D;1的从服务器才会参与消息的读负载。</p></li><li><p>Producer与NameServer集群中的其中一个节点（随机选择）建立长连接，定期从NameServer获取Topic路由信息，并向提供Topic 服务的Master建立长连接，且定时向Master发送心跳。Producer完全无状态，可集群部署。</p></li><li><p>Consumer与NameServer集群中的其中一个节点（随机选择）建立长连接，定期从NameServer获取Topic路由信息，并向提供Topic服务的Master、Slave建立长连接，且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息，也可以从Slave订阅消息，消费者在向Master拉取消息时，Master服务器会根据拉取偏移量与最大偏移量的距离（判断是否读老消息，产生读I&#x2F;O），以及从服务器是否可读等因素建议下一次是从Master还是Slave拉取。</p></li></ul><p>结合部署架构图，描述集群工作流程：</p><ul><li>启动NameServer，NameServer起来后监听端口，等待Broker、Producer、Consumer连上来，相当于一个路由控制中心。</li><li>Broker启动，跟所有的NameServer保持长连接，定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后，NameServer集群中就有Topic跟Broker的映射关系。</li><li>收发消息前，先创建Topic，创建Topic时需要指定该Topic要存储在哪些Broker上，也可以在发送消息时自动创建Topic。</li><li>Producer发送消息，启动时先跟NameServer集群中的其中一台建立长连接，并从NameServer中获取当前发送的Topic存在哪些Broker上，轮询从队列列表中选择一个队列，然后与队列所在的Broker建立长连接从而向Broker发消息。</li><li>Consumer跟Producer类似，跟其中一台NameServer建立长连接，获取当前订阅Topic存在哪些Broker上，然后直接跟Broker建立连接通道，开始消费消息。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;架构设计&quot;&gt;&lt;a href=&quot;#架构设计&quot; class=&quot;headerlink&quot; title=&quot;架构设计&quot;&gt;&lt;/a&gt;架构设计&lt;/h1&gt;&lt;hr&gt;
&lt;h2 id=&quot;1-技术架构&quot;&gt;&lt;a href=&quot;#1-技术架构&quot; class=&quot;headerlink&quot; title=&quot;</summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="架构设计" scheme="https://nydia.github.io/tags/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ最佳实践</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/cn/best_practice/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/cn/best_practice/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T12:56:18.283Z</updated>
    
    <content type="html"><![CDATA[<h1 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h1><hr><h2 id="1-生产者"><a href="#1-生产者" class="headerlink" title="1   生产者"></a>1   生产者</h2><h3 id="1-1-发送消息注意事项"><a href="#1-1-发送消息注意事项" class="headerlink" title="1.1 发送消息注意事项"></a>1.1 发送消息注意事项</h3><h4 id="1-Tags的使用"><a href="#1-Tags的使用" class="headerlink" title="1  Tags的使用"></a>1  Tags的使用</h4><p>一个应用尽可能用一个Topic，而消息子类型则可以用tags来标识。tags可以由应用自由设置，只有生产者在发送消息设置了tags，消费方在订阅消息时才可以利用tags通过broker做消息过滤：message.setTags(“TagA”)。  </p><h4 id="2-Keys的使用"><a href="#2-Keys的使用" class="headerlink" title="2 Keys的使用"></a>2 Keys的使用</h4><p>每个消息在业务层面的唯一标识码要设置到keys字段，方便将来定位消息丢失问题。服务器会为每个消息创建索引（哈希索引），应用可以通过topic、key来查询这条消息内容，以及消息被谁消费。由于是哈希索引，请务必保证key尽可能唯一，这样可以避免潜在的哈希冲突。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 订单Id   </span></span><br><span class="line"><span class="type">String</span> <span class="variable">orderId</span> <span class="operator">=</span> <span class="string">&quot;20034568923546&quot;</span>;   </span><br><span class="line">message.setKeys(orderId);   </span><br></pre></td></tr></table></figure><h4 id="3-日志的打印"><a href="#3-日志的打印" class="headerlink" title="3 日志的打印"></a>3 日志的打印</h4><p>消息发送成功或者失败要打印消息日志，务必要打印SendResult和key字段。send消息方法只要不抛异常，就代表发送成功。发送成功会有多个状态，在sendResult里定义。以下对每个状态进行说明：     </p><ul><li><strong>SEND_OK</strong></li></ul><p>消息发送成功。要注意的是消息发送成功也不意味着它是可靠的。要确保不会丢失任何消息，还应启用同步Master服务器或同步刷盘，即SYNC_MASTER或SYNC_FLUSH。</p><ul><li><strong>FLUSH_DISK_TIMEOUT</strong></li></ul><p>消息发送成功但是服务器刷盘超时。此时消息已经进入服务器队列（内存），只有服务器宕机，消息才会丢失。消息存储配置参数中可以设置刷盘方式和同步刷盘时间长度，如果Broker服务器设置了刷盘方式为同步刷盘，即FlushDiskType&#x3D;SYNC_FLUSH（默认为异步刷盘方式），当Broker服务器未在同步刷盘时间内（默认为5s）完成刷盘，则将返回该状态——刷盘超时。</p><ul><li><strong>FLUSH_SLAVE_TIMEOUT</strong></li></ul><p>消息发送成功，但是服务器同步到Slave时超时。此时消息已经进入服务器队列，只有服务器宕机，消息才会丢失。如果Broker服务器的角色是同步Master，即SYNC_MASTER（默认是异步Master即ASYNC_MASTER），并且从Broker服务器未在同步刷盘时间（默认为5秒）内完成与主服务器的同步，则将返回该状态——数据同步到Slave服务器超时。</p><ul><li><strong>SLAVE_NOT_AVAILABLE</strong></li></ul><p>消息发送成功，但是此时Slave不可用。如果Broker服务器的角色是同步Master，即SYNC_MASTER（默认是异步Master服务器即ASYNC_MASTER），但没有配置slave Broker服务器，则将返回该状态——无Slave服务器可用。</p><h3 id="1-2-消息发送失败处理方式"><a href="#1-2-消息发送失败处理方式" class="headerlink" title="1.2 消息发送失败处理方式"></a>1.2 消息发送失败处理方式</h3><p>Producer的send方法本身支持内部重试，重试逻辑如下：</p><ul><li>至多重试2次（同步发送为2次，异步发送为0次）。</li><li>如果发送失败，则轮转到下一个Broker。这个方法的总耗时时间不超过sendMsgTimeout设置的值，默认10s。</li><li>如果本身向broker发送消息产生超时异常，就不会再重试。</li></ul><p>以上策略也是在一定程度上保证了消息可以发送成功。如果业务对消息可靠性要求比较高，建议应用增加相应的重试逻辑：比如调用send同步方法发送失败时，则尝试将消息存储到db，然后由后台线程定时重试，确保消息一定到达Broker。</p><p>上述db重试方式为什么没有集成到MQ客户端内部做，而是要求应用自己去完成，主要基于以下几点考虑：首先，MQ的客户端设计为无状态模式，方便任意的水平扩展，且对机器资源的消耗仅仅是cpu、内存、网络。其次，如果MQ客户端内部集成一个KV存储模块，那么数据只有同步落盘才能较可靠，而同步落盘本身性能开销较大，所以通常会采用异步落盘，又由于应用关闭过程不受MQ运维人员控制，可能经常会发生 kill -9 这样暴力方式关闭，造成数据没有及时落盘而丢失。第三，Producer所在机器的可靠性较低，一般为虚拟机，不适合存储重要数据。综上，建议重试过程交由应用来控制。</p><h3 id="1-3选择oneway形式发送"><a href="#1-3选择oneway形式发送" class="headerlink" title="1.3选择oneway形式发送"></a>1.3选择oneway形式发送</h3><p>通常消息的发送是这样一个过程：</p><ul><li>客户端发送请求到服务器</li><li>服务器处理请求</li><li>服务器向客户端返回应答</li></ul><p>所以，一次消息发送的耗时时间是上述三个步骤的总和，而某些场景要求耗时非常短，但是对可靠性要求并不高，例如日志收集类应用，此类应用可以采用oneway形式调用，oneway形式只发送请求不等待应答，而发送请求在客户端实现层面仅仅是一个操作系统系统调用的开销，即将数据写入客户端的socket缓冲区，此过程耗时通常在微秒级。</p><h2 id="2-消费者"><a href="#2-消费者" class="headerlink" title="2   消费者"></a>2   消费者</h2><h3 id="2-1-消费过程幂等"><a href="#2-1-消费过程幂等" class="headerlink" title="2.1 消费过程幂等"></a>2.1 消费过程幂等</h3><p>RocketMQ无法避免消息重复（Exactly-Once），所以如果业务对消费重复非常敏感，务必要在业务层面进行去重处理。可以借助关系数据库进行去重。首先需要确定消息的唯一键，可以是msgId，也可以是消息内容中的唯一标识字段，例如订单Id等。在消费之前判断唯一键是否在关系数据库中存在。如果不存在则插入，并消费，否则跳过。（实际过程要考虑原子性问题，判断是否存在可以尝试插入，如果报主键冲突，则插入失败，直接跳过）</p><p>msgId一定是全局唯一标识符，但是实际使用中，可能会存在相同的消息有两个不同msgId的情况（消费者主动重发、因客户端重投机制导致的重复等），这种情况就需要使业务字段进行重复消费。</p><h3 id="2-2-消费速度慢的处理方式"><a href="#2-2-消费速度慢的处理方式" class="headerlink" title="2.2 消费速度慢的处理方式"></a>2.2 消费速度慢的处理方式</h3><h4 id="1-提高消费并行度"><a href="#1-提高消费并行度" class="headerlink" title="1 提高消费并行度"></a>1 提高消费并行度</h4><p>绝大部分消息消费行为都属于 IO 密集型，即可能是操作数据库，或者调用 RPC，这类消费行为的消费速度在于后端数据库或者外系统的吞吐量，通过增加消费并行度，可以提高总的消费吞吐量，但是并行度增加到一定程度，反而会下降。所以，应用必须要设置合理的并行度。 如下有几种修改消费并行度的方法：</p><ul><li>同一个 ConsumerGroup 下，通过增加 Consumer 实例数量来提高并行度（需要注意的是超过订阅队列数的 Consumer 实例无效）。可以通过加机器，或者在已有机器启动多个进程的方式。</li><li>提高单个 Consumer 的消费并行线程，通过修改参数 consumeThreadMin、consumeThreadMax实现。</li></ul><h4 id="2-批量方式消费"><a href="#2-批量方式消费" class="headerlink" title="2   批量方式消费"></a>2   批量方式消费</h4><p>某些业务流程如果支持批量方式消费，则可以很大程度上提高消费吞吐量，例如订单扣款类应用，一次处理一个订单耗时 1 s，一次处理 10 个订单可能也只耗时 2 s，这样即可大幅度提高消费的吞吐量，通过设置 consumer的 consumeMessageBatchMaxSize 返个参数，默认是 1，即一次只消费一条消息，例如设置为 N，那么每次消费的消息数小于等于 N。</p><h4 id="3-跳过非重要消息"><a href="#3-跳过非重要消息" class="headerlink" title="3   跳过非重要消息"></a>3   跳过非重要消息</h4><p>发生消息堆积时，如果消费速度一直追不上发送速度，如果业务对数据要求不高的话，可以选择丢弃不重要的消息。例如，当某个队列的消息数堆积到100000条以上，则尝试丢弃部分或全部消息，这样就可以快速追上发送消息的速度。示例代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ConsumeConcurrentlyStatus <span class="title function_">consumeMessage</span><span class="params">(</span></span><br><span class="line"><span class="params">        List&lt;MessageExt&gt; msgs,</span></span><br><span class="line"><span class="params">        ConsumeConcurrentlyContext context)</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">offset</span> <span class="operator">=</span> msgs.get(<span class="number">0</span>).getQueueOffset();</span><br><span class="line">    <span class="type">String</span> <span class="variable">maxOffset</span> <span class="operator">=</span></span><br><span class="line">            msgs.get(<span class="number">0</span>).getProperty(Message.PROPERTY_MAX_OFFSET);</span><br><span class="line">    <span class="type">long</span> <span class="variable">diff</span> <span class="operator">=</span> Long.parseLong(maxOffset) - offset;</span><br><span class="line">    <span class="keyword">if</span> (diff &gt; <span class="number">100000</span>) &#123;</span><br><span class="line">        <span class="comment">// TODO 消息堆积情况的特殊处理</span></span><br><span class="line">        <span class="keyword">return</span> ConsumeConcurrentlyStatus.CONSUME_SUCCESS;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// TODO 正常消费过程</span></span><br><span class="line">    <span class="keyword">return</span> ConsumeConcurrentlyStatus.CONSUME_SUCCESS;</span><br><span class="line">&#125;    </span><br></pre></td></tr></table></figure><h4 id="4-优化每条消息消费过程"><a href="#4-优化每条消息消费过程" class="headerlink" title="4 优化每条消息消费过程"></a>4 优化每条消息消费过程</h4><p>举例如下，某条消息的消费过程如下：</p><ul><li>根据消息从 DB 查询【数据 1】</li><li>根据消息从 DB 查询【数据 2】</li><li>复杂的业务计算</li><li>向 DB 插入【数据 3】</li><li>向 DB 插入【数据 4】</li></ul><p>这条消息的消费过程中有4次与 DB的 交互，如果按照每次 5ms 计算，那么总共耗时 20ms，假设业务计算耗时 5ms，那么总过耗时 25ms，所以如果能把 4 次 DB 交互优化为 2 次，那么总耗时就可以优化到 15ms，即总体性能提高了 40%。所以应用如果对时延敏感的话，可以把DB部署在SSD硬盘，相比于SCSI磁盘，前者的RT会小很多。</p><h3 id="2-3-消费打印日志"><a href="#2-3-消费打印日志" class="headerlink" title="2.3 消费打印日志"></a>2.3 消费打印日志</h3><p>如果消息量较少，建议在消费入口方法打印消息，消费耗时等，方便后续排查问题。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ConsumeConcurrentlyStatus <span class="title function_">consumeMessage</span><span class="params">(</span></span><br><span class="line"><span class="params">         List&lt;MessageExt&gt; msgs,</span></span><br><span class="line"><span class="params">         ConsumeConcurrentlyContext context)</span> &#123;</span><br><span class="line">     log.info(<span class="string">&quot;RECEIVE_MSG_BEGIN: &quot;</span> + msgs.toString());</span><br><span class="line">     <span class="comment">// TODO 正常消费过程</span></span><br><span class="line">     <span class="keyword">return</span> ConsumeConcurrentlyStatus.CONSUME_SUCCESS;</span><br><span class="line"> &#125;   </span><br></pre></td></tr></table></figure><p>如果能打印每条消息消费耗时，那么在排查消费慢等线上问题时，会更方便。</p><h3 id="2-4-其他消费建议"><a href="#2-4-其他消费建议" class="headerlink" title="2.4 其他消费建议"></a>2.4 其他消费建议</h3><h4 id="1-关于消费者和订阅"><a href="#1-关于消费者和订阅" class="headerlink" title="1 关于消费者和订阅"></a>1 关于消费者和订阅</h4><p>第一件需要注意的事情是，不同的消费者组可以独立的消费一些 topic，并且每个消费者组都有自己的消费偏移量，请确保同一组内的每个消费者订阅信息保持一致。</p><h4 id="2-关于有序消息"><a href="#2-关于有序消息" class="headerlink" title="2 关于有序消息"></a>2 关于有序消息</h4><p>消费者将锁定每个消息队列，以确保他们被逐个消费，虽然这将会导致性能下降，但是当你关心消息顺序的时候会很有用。我们不建议抛出异常，你可以返回 ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT 作为替代。</p><h4 id="3-关于并发消费"><a href="#3-关于并发消费" class="headerlink" title="3 关于并发消费"></a>3 关于并发消费</h4><p>顾名思义，消费者将并发消费这些消息，建议你使用它来获得良好性能，我们不建议抛出异常，你可以返回 ConsumeConcurrentlyStatus.RECONSUME_LATER 作为替代。</p><h4 id="4-关于消费状态Consume-Status"><a href="#4-关于消费状态Consume-Status" class="headerlink" title="4 关于消费状态Consume Status"></a>4 关于消费状态Consume Status</h4><p>对于并发的消费监听器，你可以返回 RECONSUME_LATER 来通知消费者现在不能消费这条消息，并且希望可以稍后重新消费它。然后，你可以继续消费其他消息。对于有序的消息监听器，因为你关心它的顺序，所以不能跳过消息，但是你可以返回SUSPEND_CURRENT_QUEUE_A_MOMENT 告诉消费者等待片刻。</p><h4 id="5-关于Blocking"><a href="#5-关于Blocking" class="headerlink" title="5 关于Blocking"></a>5 关于Blocking</h4><p>不建议阻塞监听器，因为它会阻塞线程池，并最终可能会终止消费进程</p><h4 id="6-关于线程数设置"><a href="#6-关于线程数设置" class="headerlink" title="6 关于线程数设置"></a>6 关于线程数设置</h4><p>消费者使用 ThreadPoolExecutor 在内部对消息进行消费，所以你可以通过设置 setConsumeThreadMin 或 setConsumeThreadMax 来改变它。</p><h4 id="7-关于消费位点"><a href="#7-关于消费位点" class="headerlink" title="7 关于消费位点"></a>7 关于消费位点</h4><p>当建立一个新的消费者组时，需要决定是否需要消费已经存在于 Broker 中的历史消息CONSUME_FROM_LAST_OFFSET 将会忽略历史消息，并消费之后生成的任何消息。CONSUME_FROM_FIRST_OFFSET 将会消费每个存在于 Broker 中的信息。你也可以使用 CONSUME_FROM_TIMESTAMP 来消费在指定时间戳后产生的消息。</p><h2 id="3-Broker"><a href="#3-Broker" class="headerlink" title="3   Broker"></a>3   Broker</h2><h3 id="3-1-Broker-角色"><a href="#3-1-Broker-角色" class="headerlink" title="3.1 Broker 角色"></a>3.1 Broker 角色</h3><p>  Broker 角色分为 ASYNC_MASTER（异步主机）、SYNC_MASTER（同步主机）以及SLAVE（从机）。如果对消息的可靠性要求比较严格，可以采用 SYNC_MASTER加SLAVE的部署方式。如果对消息可靠性要求不高，可以采用ASYNC_MASTER加SLAVE的部署方式。如果只是测试方便，则可以选择仅ASYNC_MASTER或仅SYNC_MASTER的部署方式。</p><h3 id="3-2-FlushDiskType"><a href="#3-2-FlushDiskType" class="headerlink" title="3.2 FlushDiskType"></a>3.2 FlushDiskType</h3><p> SYNC_FLUSH（同步刷新）相比于ASYNC_FLUSH（异步处理）会损失很多性能，但是也更可靠，所以需要根据实际的业务场景做好权衡。</p><h3 id="3-3-Broker-配置"><a href="#3-3-Broker-配置" class="headerlink" title="3.3 Broker 配置"></a>3.3 Broker 配置</h3><table><thead><tr><th>参数名</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td>listenPort</td><td>10911</td><td>接受客户端连接的监听端口</td></tr><tr><td>namesrvAddr</td><td>null</td><td>nameServer 地址</td></tr><tr><td>brokerIP1</td><td>网卡的 InetAddress</td><td>当前 broker 监听的 IP</td></tr><tr><td>brokerIP2</td><td>跟 brokerIP1 一样</td><td>存在主从 broker 时，如果在 broker 主节点上配置了 brokerIP2 属性，broker 从节点会连接主节点配置的 brokerIP2 进行同步</td></tr><tr><td>brokerName</td><td>null</td><td>broker 的名称</td></tr><tr><td>brokerClusterName</td><td>DefaultCluster</td><td>本 broker 所属的 Cluser 名称</td></tr><tr><td>brokerId</td><td>0</td><td>broker id, 0 表示 master, 其他的正整数表示 slave</td></tr><tr><td>storePathCommitLog</td><td>$HOME&#x2F;store&#x2F;commitlog&#x2F;</td><td>存储 commit log 的路径</td></tr><tr><td>storePathConsumerQueue</td><td>$HOME&#x2F;store&#x2F;consumequeue&#x2F;</td><td>存储 consume queue 的路径</td></tr><tr><td>mappedFileSizeCommitLog</td><td>1024 * 1024 * 1024(1G)</td><td>commit log 的映射文件大小</td></tr><tr><td>deleteWhen</td><td>04</td><td>在每天的什么时间删除已经超过文件保留时间的 commit log</td></tr><tr><td>fileReservedTime</td><td>72</td><td>以小时计算的文件保留时间</td></tr><tr><td>brokerRole</td><td>ASYNC_MASTER</td><td>SYNC_MASTER&#x2F;ASYNC_MASTER&#x2F;SLAVE</td></tr><tr><td>flushDiskType</td><td>ASYNC_FLUSH</td><td>SYNC_FLUSH&#x2F;ASYNC_FLUSH SYNC_FLUSH 模式下的 broker 保证在收到确认生产者之前将消息刷盘。ASYNC_FLUSH 模式下的 broker 则利用刷盘一组消息的模式，可以取得更好的性能。</td></tr></tbody></table><h2 id="4-NameServer"><a href="#4-NameServer" class="headerlink" title="4  NameServer"></a>4  NameServer</h2><p>RocketMQ 中，Name Servers 被设计用来做简单的路由管理。其职责包括：</p><ul><li>Brokers 定期向每个名称服务器注册路由数据。</li><li>名称服务器为客户端，包括生产者，消费者和命令行客户端提供最新的路由信息。<br>​<br>​</li></ul><h2 id="5-客户端配置"><a href="#5-客户端配置" class="headerlink" title="5 客户端配置"></a>5 客户端配置</h2><p> 相对于RocketMQ的Broker集群，生产者和消费者都是客户端。本小节主要描述生产者和消费者公共的行为配置。</p><h3 id="5-1-客户端寻址方式"><a href="#5-1-客户端寻址方式" class="headerlink" title="5.1 客户端寻址方式"></a>5.1 客户端寻址方式</h3><p>RocketMQ可以令客户端找到Name Server, 然后通过Name Server再找到Broker。如下所示有多种配置方式，优先级由高到低，高优先级会覆盖低优先级。</p><ul><li>代码中指定Name Server地址，多个namesrv地址之间用分号分割</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">producer.setNamesrvAddr(<span class="string">&quot;192.168.0.1:9876;192.168.0.2:9876&quot;</span>);  </span><br><span class="line"></span><br><span class="line">consumer.setNamesrvAddr(<span class="string">&quot;192.168.0.1:9876;192.168.0.2:9876&quot;</span>);</span><br></pre></td></tr></table></figure><ul><li>Java启动参数中指定Name Server地址</li></ul><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-Drocketmq.namesrv.addr=192.168.0.1:9876;192.168.0.2:9876  </span><br></pre></td></tr></table></figure><ul><li>环境变量指定Name Server地址</li></ul><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">export   NAMESRV_ADDR=192.168.0.1:9876;192.168.0.2:9876   </span><br></pre></td></tr></table></figure><ul><li>HTTP静态服务器寻址（默认）</li></ul><p>客户端启动后，会定时访问一个静态HTTP服务器，地址如下：<a href="http://jmenv.tbsite.net:8080/rocketmq/nsaddr">http://jmenv.tbsite.net:8080/rocketmq/nsaddr</a>，这个URL的返回内容如下：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">192.168.0.1:9876;192.168.0.2:9876   </span><br></pre></td></tr></table></figure><p>客户端默认每隔2分钟访问一次这个HTTP服务器，并更新本地的Name Server地址。URL已经在代码中硬编码，可通过修改&#x2F;etc&#x2F;hosts文件来改变要访问的服务器，例如在&#x2F;etc&#x2F;hosts增加如下配置：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">10.232.22.67    jmenv.taobao.net   </span><br></pre></td></tr></table></figure><p>推荐使用HTTP静态服务器寻址方式，好处是客户端部署简单，且Name Server集群可以热升级。</p><h3 id="5-2-客户端配置"><a href="#5-2-客户端配置" class="headerlink" title="5.2 客户端配置"></a>5.2 客户端配置</h3><p>DefaultMQProducer、TransactionMQProducer、DefaultMQPushConsumer、DefaultMQPullConsumer都继承于ClientConfig类，ClientConfig为客户端的公共配置类。客户端的配置都是get、set形式，每个参数都可以用spring来配置，也可以在代码中配置，例如namesrvAddr这个参数可以这样配置，producer.setNamesrvAddr(“192.168.0.1:9876”)，其他参数同理。</p><h4 id="1-客户端的公共配置"><a href="#1-客户端的公共配置" class="headerlink" title="1  客户端的公共配置"></a>1  客户端的公共配置</h4><p>| 参数名                        | 默认值  | 说明                                                         |<br>| ————————-d—- | ——- | ———————————————————— |<br>| namesrvAddr                   |         | Name Server地址列表，多个NameServer地址用分号隔开            |<br>| clientIP                      | 本机IP  | 客户端本机IP地址，某些机器会发生无法识别客户端IP地址情况，需要应用在代码中强制指定 |<br>| instanceName                  | DEFAULT | 客户端实例名称，客户端创建的多个Producer、Consumer实际是共用一个内部实例（这个实例包含网络连接、线程资源等） |<br>| clientCallbackExecutorThreads | 4       | 通信层异步回调线程数                                         |<br>| pollNameServerInteval         | 30000   | 轮询Name Server间隔时间，单位毫秒                            |<br>| heartbeatBrokerInterval       | 30000   | 向Broker发送心跳间隔时间，单位毫秒                           |<br>| persistConsumerOffsetInterval | 5000    | 持久化Consumer消费进度间隔时间，单位毫秒                     |</p><h4 id="2-Producer配置"><a href="#2-Producer配置" class="headerlink" title="2  Producer配置"></a>2  Producer配置</h4><table><thead><tr><th>参数名</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td>producerGroup</td><td>DEFAULT_PRODUCER</td><td>Producer组名，多个Producer如果属于一个应用，发送同样的消息，则应该将它们归为同一组</td></tr><tr><td>createTopicKey</td><td>TBW102</td><td>在发送消息时，自动创建服务器不存在的topic，需要指定Key，该Key可用于配置发送消息所在topic的默认路由。</td></tr><tr><td>defaultTopicQueueNums</td><td>4</td><td>在发送消息，自动创建服务器不存在的topic时，默认创建的队列数</td></tr><tr><td>sendMsgTimeout</td><td>10000</td><td>发送消息超时时间，单位毫秒</td></tr><tr><td>compressMsgBodyOverHowmuch</td><td>4096</td><td>消息Body超过多大开始压缩（Consumer收到消息会自动解压缩），单位字节</td></tr><tr><td>retryAnotherBrokerWhenNotStoreOK</td><td>FALSE</td><td>如果发送消息返回sendResult，但是sendStatus!&#x3D;SEND_OK，是否重试发送</td></tr><tr><td>retryTimesWhenSendFailed</td><td>2</td><td>如果消息发送失败，最大重试次数，该参数只对同步发送模式起作用</td></tr><tr><td>maxMessageSize</td><td>4MB</td><td>客户端限制的消息大小，超过报错，同时服务端也会限制，所以需要跟服务端配合使用。</td></tr><tr><td>transactionCheckListener</td><td></td><td>事务消息回查监听器，如果发送事务消息，必须设置</td></tr><tr><td>checkThreadPoolMinSize</td><td>1</td><td>Broker回查Producer事务状态时，线程池最小线程数</td></tr><tr><td>checkThreadPoolMaxSize</td><td>1</td><td>Broker回查Producer事务状态时，线程池最大线程数</td></tr><tr><td>checkRequestHoldMax</td><td>2000</td><td>Broker回查Producer事务状态时，Producer本地缓冲请求队列大小</td></tr><tr><td>RPCHook</td><td>null</td><td>该参数是在Producer创建时传入的，包含消息发送前的预处理和消息响应后的处理两个接口，用户可以在第一个接口中做一些安全控制或者其他操作。</td></tr></tbody></table><h4 id="3-PushConsumer配置"><a href="#3-PushConsumer配置" class="headerlink" title="3  PushConsumer配置"></a>3  PushConsumer配置</h4><table><thead><tr><th>参数名</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td>consumerGroup</td><td>DEFAULT_CONSUMER</td><td>Consumer组名，多个Consumer如果属于一个应用，订阅同样的消息，且消费逻辑一致，则应该将它们归为同一组</td></tr><tr><td>messageModel</td><td>CLUSTERING</td><td>消费模型支持集群消费和广播消费两种</td></tr><tr><td>consumeFromWhere</td><td>CONSUME_FROM_LAST_OFFSET</td><td>Consumer启动后，默认从上次消费的位置开始消费，这包含两种情况：一种是上次消费的位置未过期，则消费从上次中止的位置进行；一种是上次消费位置已经过期，则从当前队列第一条消息开始消费</td></tr><tr><td>consumeTimestamp</td><td>半个小时前</td><td>只有当consumeFromWhere值为CONSUME_FROM_TIMESTAMP时才起作用。</td></tr><tr><td>allocateMessageQueueStrategy</td><td>AllocateMessageQueueAveragely</td><td>Rebalance算法实现策略</td></tr><tr><td>subscription</td><td></td><td>订阅关系</td></tr><tr><td>messageListener</td><td></td><td>消息监听器</td></tr><tr><td>offsetStore</td><td></td><td>消费进度存储</td></tr><tr><td>consumeThreadMin</td><td>10</td><td>消费线程池最小线程数</td></tr><tr><td>consumeThreadMax</td><td>20</td><td>消费线程池最大线程数</td></tr><tr><td>consumeConcurrentlyMaxSpan</td><td>2000</td><td>单队列并行消费允许的最大跨度</td></tr><tr><td>pullThresholdForQueue</td><td>1000</td><td>拉消息本地队列缓存消息最大数</td></tr><tr><td>pullInterval</td><td>0</td><td>拉消息间隔，由于是长轮询，所以为0，但是如果应用为了流控，也可以设置大于0的值，单位毫秒</td></tr><tr><td>consumeMessageBatchMaxSize</td><td>1</td><td>批量消费，一次消费多少条消息</td></tr><tr><td>pullBatchSize</td><td>32</td><td>批量拉消息，一次最多拉多少条</td></tr></tbody></table><h4 id="4-PullConsumer配置"><a href="#4-PullConsumer配置" class="headerlink" title="4  PullConsumer配置"></a>4  PullConsumer配置</h4><table><thead><tr><th>参数名</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td>consumerGroup</td><td>DEFAULT_CONSUMER</td><td>Consumer组名，多个Consumer如果属于一个应用，订阅同样的消息，且消费逻辑一致，则应该将它们归为同一组</td></tr><tr><td>brokerSuspendMaxTimeMillis</td><td>20000</td><td>长轮询，Consumer拉消息请求在Broker挂起最长时间，单位毫秒</td></tr><tr><td>consumerTimeoutMillisWhenSuspend</td><td>30000</td><td>长轮询，Consumer拉消息请求在Broker挂起超过指定时间，客户端认为超时，单位毫秒</td></tr><tr><td>consumerPullTimeoutMillis</td><td>10000</td><td>非长轮询，拉消息超时时间，单位毫秒</td></tr><tr><td>messageModel</td><td>BROADCASTING</td><td>消息支持两种模式：集群消费和广播消费</td></tr><tr><td>messageQueueListener</td><td></td><td>监听队列变化</td></tr><tr><td>offsetStore</td><td></td><td>消费进度存储</td></tr><tr><td>registerTopics</td><td></td><td>注册的topic集合</td></tr><tr><td>allocateMessageQueueStrategy</td><td>AllocateMessageQueueAveragely</td><td>Rebalance算法实现策略</td></tr></tbody></table><h4 id="5-Message数据结构"><a href="#5-Message数据结构" class="headerlink" title="5  Message数据结构"></a>5  Message数据结构</h4><table><thead><tr><th>字段名</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td>Topic</td><td>null</td><td>必填，消息所属topic的名称</td></tr><tr><td>Body</td><td>null</td><td>必填，消息体</td></tr><tr><td>Tags</td><td>null</td><td>选填，消息标签，方便服务器过滤使用。目前只支持每个消息设置一个tag</td></tr><tr><td>Keys</td><td>null</td><td>选填，代表这条消息的业务关键词，服务器会根据keys创建哈希索引，设置后，可以在Console系统根据Topic、Keys来查询消息，由于是哈希索引，请尽可能保证key唯一，例如订单号，商品Id等。</td></tr><tr><td>Flag</td><td>0</td><td>选填，完全由应用来设置，RocketMQ不做干预</td></tr><tr><td>DelayTimeLevel</td><td>0</td><td>选填，消息延时级别，0表示不延时，大于0会延时特定的时间才会被消费</td></tr><tr><td>WaitStoreMsgOK</td><td>TRUE</td><td>选填，表示消息是否在服务器落盘后才返回应答。</td></tr></tbody></table><h2 id="6-系统配置"><a href="#6-系统配置" class="headerlink" title="6  系统配置"></a>6  系统配置</h2><p>本小节主要介绍系统（JVM&#x2F;OS）相关的配置。</p><h3 id="6-1-JVM选项"><a href="#6-1-JVM选项" class="headerlink" title="6.1 JVM选项"></a>6.1 JVM选项</h3><p> 推荐使用最新发布的JDK 1.8版本。通过设置相同的Xms和Xmx值来防止JVM调整堆大小以获得更好的性能。简单的JVM配置如下所示：<br>​<br>​<code>​ ​-server -Xms8g -Xmx8g -Xmn4g    ​</code><br>​<br>​<br>如果您不关心RocketMQ Broker的启动时间，还有一种更好的选择，就是通过“预触摸”Java堆以确保在JVM初始化期间每个页面都将被分配。那些不关心启动时间的人可以启用它：<br>​ -XX:+AlwaysPreTouch<br>禁用偏置锁定可能会减少JVM暂停，<br>​ -XX:-UseBiasedLocking<br>至于垃圾回收，建议使用带JDK 1.8的G1收集器。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">-XX:+UseG1GC -XX:G1HeapRegionSize=16m   </span><br><span class="line">-XX:G1ReservePercent=25 </span><br><span class="line">-XX:InitiatingHeapOccupancyPercent=30</span><br></pre></td></tr></table></figure><p> 这些GC选项看起来有点激进，但事实证明它在我们的生产环境中具有良好的性能。另外不要把-XX:MaxGCPauseMillis的值设置太小，否则JVM将使用一个小的年轻代来实现这个目标，这将导致非常频繁的minor GC，所以建议使用rolling GC日志文件：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">-XX:+UseGCLogFileRotation   </span><br><span class="line">-XX:NumberOfGCLogFiles=5 </span><br><span class="line">-XX:GCLogFileSize=30m</span><br></pre></td></tr></table></figure><p>如果写入GC文件会增加代理的延迟，可以考虑将GC日志文件重定向到内存文件系统：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-Xloggc:/dev/shm/mq_gc_%p.log123   </span><br></pre></td></tr></table></figure><h3 id="6-2-Linux内核参数"><a href="#6-2-Linux内核参数" class="headerlink" title="6.2 Linux内核参数"></a>6.2 Linux内核参数</h3><p> os.sh脚本在bin文件夹中列出了许多内核参数，可以进行微小的更改然后用于生产用途。下面的参数需要注意，更多细节请参考&#x2F;proc&#x2F;sys&#x2F;vm&#x2F;*的<a href="https://www.kernel.org/doc/Documentation/sysctl/vm.txt">文档</a></p><ul><li><strong>vm.extra_free_kbytes</strong>，告诉VM在后台回收（kswapd）启动的阈值与直接回收（通过分配进程）的阈值之间保留额外的可用内存。RocketMQ使用此参数来避免内存分配中的长延迟。（与具体内核版本相关）</li><li><strong>vm.min_free_kbytes</strong>，如果将其设置为低于1024KB，将会巧妙的将系统破坏，并且系统在高负载下容易出现死锁。</li><li><strong>vm.max_map_count</strong>，限制一个进程可能具有的最大内存映射区域数。RocketMQ将使用mmap加载CommitLog和ConsumeQueue，因此建议将为此参数设置较大的值。（agressiveness –&gt; aggressiveness）</li><li><strong>vm.swappiness</strong>，定义内核交换内存页面的积极程度。较高的值会增加攻击性，较低的值会减少交换量。建议将值设置为10来避免交换延迟。</li><li><strong>File descriptor limits</strong>，RocketMQ需要为文件（CommitLog和ConsumeQueue）和网络连接打开文件描述符。我们建议设置文件描述符的值为655350。</li><li><a href="https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Performance_Tuning_Guide/ch06s04s02.html">Disk scheduler</a>，RocketMQ建议使用I&#x2F;O截止时间调度器，它试图为请求提供有保证的延迟。<br><a href="%5B%5D()"></a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;最佳实践&quot;&gt;&lt;a href=&quot;#最佳实践&quot; class=&quot;headerlink&quot; title=&quot;最佳实践&quot;&gt;&lt;/a&gt;最佳实践&lt;/h1&gt;&lt;hr&gt;
&lt;h2 id=&quot;1-生产者&quot;&gt;&lt;a href=&quot;#1-生产者&quot; class=&quot;headerlink&quot; title=&quot;1 </summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="最佳实践" scheme="https://nydia.github.io/tags/%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ基本概念</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/cn/concept/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/cn/concept/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T12:56:09.875Z</updated>
    
    <content type="html"><![CDATA[<h1 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h1><hr><h2 id="1-消息模型（Message-Model）"><a href="#1-消息模型（Message-Model）" class="headerlink" title="1 消息模型（Message Model）"></a>1 消息模型（Message Model）</h2><p>RocketMQ主要由 Producer、Broker、Consumer 三部分组成，其中Producer 负责生产消息，Consumer 负责消费消息，Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器，每个 Broker 可以存储多个Topic的消息，每个Topic的消息也可以分片存储于不同的 Broker。Message Queue 用于存储消息的物理地址，每个Topic中的消息地址存储于多个 Message Queue 中。ConsumerGroup 由多个Consumer 实例构成。</p><h2 id="2-消息生产者（Producer）"><a href="#2-消息生产者（Producer）" class="headerlink" title="2 消息生产者（Producer）"></a>2 消息生产者（Producer）</h2><p> 负责生产消息，一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产生的消息发送到broker服务器。RocketMQ提供多种发送方式，同步发送、异步发送、顺序发送、单向发送。同步和异步方式均需要Broker返回确认信息，单向发送不需要。</p><h2 id="3-消息消费者（Consumer）"><a href="#3-消息消费者（Consumer）" class="headerlink" title="3 消息消费者（Consumer）"></a>3 消息消费者（Consumer）</h2><p> 负责消费消息，一般是后台系统负责异步消费。一个消息消费者会从Broker服务器拉取消息、并将其提供给应用程序。从用户应用的角度而言提供了两种消费形式：拉取式消费、推动式消费。</p><h2 id="4-主题（Topic）"><a href="#4-主题（Topic）" class="headerlink" title="4 主题（Topic）"></a>4 主题（Topic）</h2><p>  表示一类消息的集合，每个主题包含若干条消息，每条消息只能属于一个主题，是RocketMQ进行消息订阅的基本单位。</p><h2 id="5-代理服务器（Broker-Server）"><a href="#5-代理服务器（Broker-Server）" class="headerlink" title="5 代理服务器（Broker Server）"></a>5 代理服务器（Broker Server）</h2><p>消息中转角色，负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据，包括消费者组、消费进度偏移和主题和队列消息等。</p><h2 id="6-名字服务（Name-Server）"><a href="#6-名字服务（Name-Server）" class="headerlink" title="6 名字服务（Name Server）"></a>6 名字服务（Name Server）</h2><p> 名称服务充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个Namesrv实例组成集群，但相互独立，没有信息交换。</p><h2 id="7-拉取式消费（Pull-Consumer）"><a href="#7-拉取式消费（Pull-Consumer）" class="headerlink" title="7 拉取式消费（Pull Consumer）"></a>7 拉取式消费（Pull Consumer）</h2><p>  Consumer消费的一种类型，应用通常主动调用Consumer的拉消息方法从Broker服务器拉消息、主动权由应用控制。一旦获取了批量消息，应用就会启动消费过程。</p><h2 id="8-推动式消费（Push-Consumer）"><a href="#8-推动式消费（Push-Consumer）" class="headerlink" title="8 推动式消费（Push Consumer）"></a>8 推动式消费（Push Consumer）</h2><p> Consumer消费的一种类型，该模式下Broker收到数据后会主动推送给消费端，该消费模式一般实时性较高。</p><h2 id="9-生产者组（Producer-Group）"><a href="#9-生产者组（Producer-Group）" class="headerlink" title="9 生产者组（Producer Group）"></a>9 生产者组（Producer Group）</h2><p>  同一类Producer的集合，这类Producer发送同一类消息且发送逻辑一致。如果发送的是事物消息且原始生产者在发送之后崩溃，则Broker服务器会联系同一生产者组的其他生产者实例以提交或回溯消费。</p><h2 id="10-消费者组（Consumer-Group）"><a href="#10-消费者组（Consumer-Group）" class="headerlink" title="10 消费者组（Consumer Group）"></a>10 消费者组（Consumer Group）</h2><p>  同一类Consumer的集合，这类Consumer通常消费同一类消息且消费逻辑一致。消费者组使得在消息消费方面，实现负载均衡和容错的目标变得非常容易。要注意的是，消费者组的消费者实例必须订阅完全相同的Topic。RocketMQ 支持两种消息模式：集群消费（Clustering）和广播消费（Broadcasting）。</p><h2 id="11-集群消费（Clustering）"><a href="#11-集群消费（Clustering）" class="headerlink" title="11 集群消费（Clustering）"></a>11 集群消费（Clustering）</h2><p>集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊消息。</p><h2 id="12-广播消费（Broadcasting）"><a href="#12-广播消费（Broadcasting）" class="headerlink" title="12 广播消费（Broadcasting）"></a>12 广播消费（Broadcasting）</h2><p>广播消费模式下，相同Consumer Group的每个Consumer实例都接收全量的消息。</p><h2 id="13-普通顺序消息（Normal-Ordered-Message）"><a href="#13-普通顺序消息（Normal-Ordered-Message）" class="headerlink" title="13 普通顺序消息（Normal Ordered Message）"></a>13 普通顺序消息（Normal Ordered Message）</h2><p>普通顺序消费模式下，消费者通过同一个消费队列收到的消息是有顺序的，不同消息队列收到的消息则可能是无顺序的。</p><h2 id="14-严格顺序消息（Strictly-Ordered-Message）"><a href="#14-严格顺序消息（Strictly-Ordered-Message）" class="headerlink" title="14 严格顺序消息（Strictly Ordered Message）"></a>14 严格顺序消息（Strictly Ordered Message）</h2><p>严格顺序消息模式下，消费者收到的所有消息均是有顺序的。</p><h2 id="15-消息（Message）"><a href="#15-消息（Message）" class="headerlink" title="15 消息（Message）"></a>15 消息（Message）</h2><p>消息系统所传输信息的物理载体，生产和消费数据的最小单位，每条消息必须属于一个主题。RocketMQ中每个消息拥有唯一的Message ID，且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能。</p><h2 id="16-标签（Tag）"><a href="#16-标签（Tag）" class="headerlink" title="16 标签（Tag）"></a>16 标签（Tag）</h2><p> 为消息设置的标志，用于同一主题下区分不同类型的消息。来自同一业务单元的消息，可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性，并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑，实现更好的扩展性。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;基本概念&quot;&gt;&lt;a href=&quot;#基本概念&quot; class=&quot;headerlink&quot; title=&quot;基本概念&quot;&gt;&lt;/a&gt;基本概念&lt;/h1&gt;&lt;hr&gt;
&lt;h2 id=&quot;1-消息模型（Message-Model）&quot;&gt;&lt;a href=&quot;#1-消息模型（Message-Mode</summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="基本概念" scheme="https://nydia.github.io/tags/%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ特性(features)</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/cn/features/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/cn/features/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T12:57:35.050Z</updated>
    
    <content type="html"><![CDATA[<h1 id="特性-features"><a href="#特性-features" class="headerlink" title="特性(features)"></a>特性(features)</h1><hr><h2 id="1-订阅与发布"><a href="#1-订阅与发布" class="headerlink" title="1 订阅与发布"></a>1 订阅与发布</h2><p>消息的发布是指某个生产者向某个topic发送消息；消息的订阅是指某个消费者关注了某个topic中带有某些tag的消息，进而从该topic消费数据。</p><h2 id="2-消息顺序"><a href="#2-消息顺序" class="headerlink" title="2 消息顺序"></a>2 消息顺序</h2><p>消息有序指的是一类消息消费时，能按照发送的顺序来消费。例如：一个订单产生了三条消息分别是订单创建、订单付款、订单完成。消费时要按照这个顺序消费才能有意义，但是同时订单之间是可以并行消费的。RocketMQ可以严格的保证消息有序。</p><p>顺序消息分为全局顺序消息与分区顺序消息，全局顺序是指某个Topic下的所有消息都要保证顺序；部分顺序消息只要保证每一组消息被顺序消费即可。</p><ul><li>全局顺序<br>对于指定的一个 Topic，所有消息按照严格的先入先出（FIFO）的顺序进行发布和消费。<br>适用场景：性能要求不高，所有的消息严格按照 FIFO 原则进行消息发布和消费的场景</li><li>分区顺序<br>对于指定的一个 Topic，所有消息根据 sharding key 进行区块分区。 同一个分区内的消息按照严格的 FIFO 顺序进行发布和消费。 Sharding key 是顺序消息中用来区分不同分区的关键字段，和普通消息的 Key 是完全不同的概念。<br>适用场景：性能要求高，以 sharding key 作为分区字段，在同一个区块中严格的按照 FIFO 原则进行消息发布和消费的场景。</li></ul><h2 id="3-消息过滤"><a href="#3-消息过滤" class="headerlink" title="3 消息过滤"></a>3 消息过滤</h2><p>RocketMQ的消费者可以根据Tag进行消息过滤，也支持自定义属性过滤。消息过滤目前是在Broker端实现的，优点是减少了对于Consumer无用消息的网络传输，缺点是增加了Broker的负担、而且实现相对复杂。</p><h2 id="4-消息可靠性"><a href="#4-消息可靠性" class="headerlink" title="4 消息可靠性"></a>4 消息可靠性</h2><p>RocketMQ支持消息的高可靠，影响消息可靠性的几种情况：</p><ol><li>Broker正常关闭</li><li>Broker异常Crash</li><li>OS Crash</li><li>机器掉电，但是能立即恢复供电情况</li><li>机器无法开机（可能是cpu、主板、内存等关键设备损坏）</li><li>磁盘设备损坏</li></ol><p>1)、2)、3)、4) 四种情况都属于硬件资源可立即恢复情况，RocketMQ在这四种情况下能保证消息不丢，或者丢失少量数据（依赖刷盘方式是同步还是异步）。</p><p>5)、6)属于单点故障，且无法恢复，一旦发生，在此单点上的消息全部丢失。RocketMQ在这两种情况下，通过异步复制，可保证99%的消息不丢，但是仍然会有极少量的消息可能丢失。通过同步双写技术可以完全避免单点，同步双写势必会影响性能，适合对消息可靠性要求极高的场合，例如与Money相关的应用。注：RocketMQ从3.0版本开始支持同步双写。</p><h2 id="5-至少一次"><a href="#5-至少一次" class="headerlink" title="5 至少一次"></a>5 至少一次</h2><p>至少一次(At least Once)指每个消息必须投递一次。Consumer先Pull消息到本地，消费完成后，才向服务器返回ack，如果没有消费一定不会ack消息，所以RocketMQ可以很好的支持此特性。</p><h2 id="6-回溯消费"><a href="#6-回溯消费" class="headerlink" title="6 回溯消费"></a>6 回溯消费</h2><p>回溯消费是指Consumer已经消费成功的消息，由于业务上需求需要重新消费，要支持此功能，Broker在向Consumer投递成功消息后，消息仍然需要保留。并且重新消费一般是按照时间维度，例如由于Consumer系统故障，恢复后需要重新消费1小时前的数据，那么Broker要提供一种机制，可以按照时间维度来回退消费进度。RocketMQ支持按照时间回溯消费，时间维度精确到毫秒。</p><h2 id="7-事务消息"><a href="#7-事务消息" class="headerlink" title="7 事务消息"></a>7 事务消息</h2><p>RocketMQ事务消息（Transactional Message）是指应用本地事务和发送消息操作可以被定义到全局事务中，要么同时成功，要么同时失败。RocketMQ的事务消息提供类似 X&#x2F;Open XA 的分布事务功能，通过事务消息能达到分布式事务的最终一致。</p><h2 id="8-定时消息"><a href="#8-定时消息" class="headerlink" title="8 定时消息"></a>8 定时消息</h2><p>定时消息（延迟队列）是指消息发送到broker后，不会立即被消费，等待特定时间投递给真正的topic。<br>broker有配置项messageDelayLevel，默认值为“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”，18个level。可以配置自定义messageDelayLevel。注意，messageDelayLevel是broker的属性，不属于某个topic。发消息时，设置delayLevel等级即可：msg.setDelayLevel(level)。level有以下三种情况：</p><ul><li>level &#x3D;&#x3D; 0，消息为非延迟消息</li><li>1&lt;&#x3D;level&lt;&#x3D;maxLevel，消息延迟特定时间，例如level&#x3D;&#x3D;1，延迟1s</li><li>level &gt; maxLevel，则level&#x3D;&#x3D; maxLevel，例如level&#x3D;&#x3D;20，延迟2h</li></ul><p>定时消息会暂存在名为SCHEDULE_TOPIC_XXXX的topic中，并根据delayTimeLevel存入特定的queue，queueId &#x3D; delayTimeLevel – 1，即一个queue只存相同延迟的消息，保证具有相同发送延迟的消息能够顺序消费。broker会调度地消费SCHEDULE_TOPIC_XXXX，将消息写入真实的topic。</p><p>需要注意的是，定时消息会在第一次写入和调度写入真实topic时都会计数，因此发送数量、tps都会变高。</p><h2 id="9-消息重试"><a href="#9-消息重试" class="headerlink" title="9 消息重试"></a>9 消息重试</h2><p>Consumer消费消息失败后，要提供一种重试机制，令消息再消费一次。Consumer消费消息失败通常可以认为有以下几种情况：</p><ul><li>由于消息本身的原因，例如反序列化失败，消息数据本身无法处理（例如话费充值，当前消息的手机号被注销，无法充值）等。这种错误通常需要跳过这条消息，再消费其它消息，而这条失败的消息即使立刻重试消费，99%也不成功，所以最好提供一种定时重试机制，即过10秒后再重试。</li><li>由于依赖的下游应用服务不可用，例如db连接不可用，外系统网络不可达等。遇到这种错误，即使跳过当前失败的消息，消费其他消息同样也会报错。这种情况建议应用sleep 30s，再消费下一条消息，这样可以减轻Broker重试消息的压力。</li></ul><p>RocketMQ会为每个消费组都设置一个Topic名称为“%RETRY%+consumerGroup”的重试队列（这里需要注意的是，这个Topic的重试队列是针对消费组，而不是针对每个Topic设置的），用于暂时保存因为各种异常而导致Consumer端无法消费的消息。考虑到异常恢复起来需要一些时间，会为重试队列设置多个重试级别，每个重试级别都有与之对应的重新投递延时，重试次数越多投递延时就越大。RocketMQ对于重试消息的处理是先保存至Topic名称为“SCHEDULE_TOPIC_XXXX”的延迟队列中，后台定时任务按照对应的时间进行Delay后重新保存至“%RETRY%+consumerGroup”的重试队列中。</p><h2 id="10-消息重投"><a href="#10-消息重投" class="headerlink" title="10 消息重投"></a>10 消息重投</h2><p>生产者在发送消息时，同步消息失败会重投，异步消息有重试，oneway没有任何保证。消息重投保证消息尽可能发送成功、不丢失，但可能会造成消息重复，消息重复在RocketMQ中是无法避免的问题。消息重复在一般情况下不会发生，当出现消息量大、网络抖动，消息重复就会是大概率事件。另外，生产者主动重发、consumer负载变化也会导致重复消息。如下方法可以设置消息重试策略：</p><ul><li>retryTimesWhenSendFailed:同步发送失败重投次数，默认为2，因此生产者会最多尝试发送retryTimesWhenSendFailed + 1次。不会选择上次失败的broker，尝试向其他broker发送，最大程度保证消息不丢。超过重投次数，抛出异常，由客户端保证消息不丢。当出现RemotingException、MQClientException和部分MQBrokerException时会重投。</li><li>retryTimesWhenSendAsyncFailed:异步发送失败重试次数，异步重试不会选择其他broker，仅在同一个broker上做重试，不保证消息不丢。</li><li>retryAnotherBrokerWhenNotStoreOK:消息刷盘（主或备）超时或slave不可用（返回状态非SEND_OK），是否尝试发送到其他broker，默认false。十分重要消息可以开启。</li></ul><h2 id="11-流量控制"><a href="#11-流量控制" class="headerlink" title="11 流量控制"></a>11 流量控制</h2><p>生产者流控，因为broker处理能力达到瓶颈；消费者流控，因为消费能力达到瓶颈。</p><p>生产者流控：</p><ul><li>commitLog文件被锁时间超过osPageCacheBusyTimeOutMills时，参数默认为1000ms，返回流控。</li><li>如果开启transientStorePoolEnable &#x3D;&#x3D; true，且broker为异步刷盘的主机，且transientStorePool中资源不足，拒绝当前send请求，返回流控。</li><li>broker每隔10ms检查send请求队列头部请求的等待时间，如果超过waitTimeMillsInSendQueue，默认200ms，拒绝当前send请求，返回流控。</li><li>broker通过拒绝send 请求方式实现流量控制。</li></ul><p>注意，生产者流控，不会尝试消息重投。</p><p>消费者流控：</p><ul><li>消费者本地缓存消息数超过pullThresholdForQueue时，默认1000。</li><li>消费者本地缓存消息大小超过pullThresholdSizeForQueue时，默认100MB。</li><li>消费者本地缓存消息跨度超过consumeConcurrentlyMaxSpan时，默认2000。</li></ul><p>消费者流控的结果是降低拉取频率。</p><h2 id="12-死信队列"><a href="#12-死信队列" class="headerlink" title="12 死信队列"></a>12 死信队列</h2><p>死信队列用于处理无法被正常消费的消息。当一条消息初次消费失败，消息队列会自动进行消息重试；达到最大重试次数后，若消费依然失败，则表明消费者在正常情况下无法正确地消费该消息，此时，消息队列 不会立刻将消息丢弃，而是将其发送到该消费者对应的特殊队列中。</p><p>RocketMQ将这种正常情况下无法被消费的消息称为死信消息（Dead-Letter Message），将存储死信消息的特殊队列称为死信队列（Dead-Letter Queue）。在RocketMQ中，可以通过使用console控制台对死信队列中的消息进行重发来使得消费者实例再次进行消费。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;特性-features&quot;&gt;&lt;a href=&quot;#特性-features&quot; class=&quot;headerlink&quot; title=&quot;特性(features)&quot;&gt;&lt;/a&gt;特性(features)&lt;/h1&gt;&lt;hr&gt;
&lt;h2 id=&quot;1-订阅与发布&quot;&gt;&lt;a href=&quot;#1-订</summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="特性(features)" scheme="https://nydia.github.io/tags/%E7%89%B9%E6%80%A7-features/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ设计(design)</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/cn/design/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/cn/design/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T12:57:24.026Z</updated>
    
    <content type="html"><![CDATA[<h1 id="设计-design"><a href="#设计-design" class="headerlink" title="设计(design)"></a>设计(design)</h1><hr><h3 id="1-消息存储"><a href="#1-消息存储" class="headerlink" title="1 消息存储"></a>1 消息存储</h3><p><img src="/image/rocketmq_design_1.png"></p><p>消息存储是RocketMQ中最为复杂和最为重要的一部分，本节将分别从RocketMQ的消息存储整体架构、PageCache与Mmap内存映射以及RocketMQ中两种不同的刷盘方式三方面来分别展开叙述。</p><h4 id="1-1-消息存储整体架构"><a href="#1-1-消息存储整体架构" class="headerlink" title="1.1 消息存储整体架构"></a>1.1 消息存储整体架构</h4><p>消息存储架构图中主要有下面三个跟消息存储相关的文件构成。</p><p>(1) CommitLog：消息主体以及元数据的存储主体，存储Producer端写入的消息主体内容,消息内容不是定长的。单个文件大小默认1G ，文件名长度为20位，左边补零，剩余为起始偏移量，比如00000000000000000000代表了第一个文件，起始偏移量为0，文件大小为1G&#x3D;1073741824；当第一个文件写满了，第二个文件为00000000001073741824，起始偏移量为1073741824，以此类推。消息主要是顺序写入日志文件，当文件满了，写入下一个文件；</p><p>(2) ConsumeQueue：消息消费队列，引入的目的主要是提高消息消费的性能，由于RocketMQ是基于主题topic的订阅模式，消息消费是针对主题进行的，如果要遍历commitlog文件中根据topic检索消息是非常低效的。Consumer即可根据ConsumeQueue来查找待消费的消息。其中，ConsumeQueue（逻辑消费队列）作为消费消息的索引，保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset，消息大小size和消息Tag的HashCode值。consumequeue文件可以看成是基于topic的commitlog索引文件，故consumequeue文件夹的组织方式如下：topic&#x2F;queue&#x2F;file三层组织结构，具体存储路径为：$HOME&#x2F;store&#x2F;consumequeue&#x2F;{topic}&#x2F;{queueId}&#x2F;{fileName}。同样consumequeue文件采取定长设计，每一个条目共20个字节，分别为8字节的commitlog物理偏移量、4字节的消息长度、8字节tag hashcode，单个文件由30W个条目组成，可以像数组一样随机访问每一个条目，每个ConsumeQueue文件大小约5.72M；</p><p>(3) IndexFile：IndexFile（索引文件）提供了一种可以通过key或时间区间来查询消息的方法。Index文件的存储位置是：$HOME \store\index${fileName}，文件名fileName是以创建时的时间戳命名的，固定的单个IndexFile文件大小约为400M，一个IndexFile可以保存 2000W个索引，IndexFile的底层存储设计为在文件系统中实现HashMap结构，故rocketmq的索引文件其底层实现为hash索引。</p><p>在上面的RocketMQ的消息存储整体架构图中可以看出，RocketMQ采用的是混合型的存储结构，即为Broker单个实例下所有的队列共用一个日志数据文件（即为CommitLog）来存储。RocketMQ的混合型存储结构(多个Topic的消息实体内容都存储于一个CommitLog中)针对Producer和Consumer分别采用了数据和索引部分相分离的存储结构，Producer发送消息至Broker端，然后Broker端使用同步或者异步的方式对消息刷盘持久化，保存至CommitLog中。只要消息被刷盘持久化至磁盘文件CommitLog中，那么Producer发送的消息就不会丢失。正因为如此，Consumer也就肯定有机会去消费这条消息。当无法拉取到消息后，可以等下一次消息拉取，同时服务端也支持长轮询模式，如果一个消息拉取请求未拉取到消息，Broker允许等待30s的时间，只要这段时间内有新消息到达，将直接返回给消费端。这里，RocketMQ的具体做法是，使用Broker端的后台服务线程—ReputMessageService不停地分发请求并异步构建ConsumeQueue（逻辑消费队列）和IndexFile（索引文件）数据。</p><h4 id="1-2-页缓存与内存映射"><a href="#1-2-页缓存与内存映射" class="headerlink" title="1.2 页缓存与内存映射"></a>1.2 页缓存与内存映射</h4><p>页缓存（PageCache)是OS对文件的缓存，用于加速对文件的读写。一般来说，程序对文件进行顺序读写的速度几乎接近于内存的读写速度，主要原因就是由于OS使用PageCache机制对读写访问操作进行了性能优化，将一部分的内存用作PageCache。对于数据的写入，OS会先写入至Cache内，随后通过异步的方式由pdflush内核线程将Cache内的数据刷盘至物理磁盘上。对于数据的读取，如果一次读取文件时出现未命中PageCache的情况，OS从物理磁盘上访问读取文件的同时，会顺序对其他相邻块的数据文件进行预读取。</p><p>在RocketMQ中，ConsumeQueue逻辑消费队列存储的数据较少，并且是顺序读取，在page cache机制的预读取作用下，Consume Queue文件的读性能几乎接近读内存，即使在有消息堆积情况下也不会影响性能。而对于CommitLog消息存储的日志数据文件来说，读取消息内容时候会产生较多的随机访问读取，严重影响性能。如果选择合适的系统IO调度算法，比如设置调度算法为“Deadline”（此时块存储采用SSD的话），随机读的性能也会有所提升。</p><p>另外，RocketMQ主要通过MappedByteBuffer对文件进行读写操作。其中，利用了NIO中的FileChannel模型将磁盘上的物理文件直接映射到用户态的内存地址中（这种Mmap的方式减少了传统IO将磁盘文件数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间来回进行拷贝的性能开销），将对文件的操作转化为直接对内存地址进行操作，从而极大地提高了文件的读写效率（正因为需要使用内存映射机制，故RocketMQ的文件存储都使用定长结构来存储，方便一次将整个文件映射至内存）。</p><h4 id="1-3-消息刷盘"><a href="#1-3-消息刷盘" class="headerlink" title="1.3 消息刷盘"></a>1.3 消息刷盘</h4><p><img src="/image/rocketmq_design_2.png"></p><p>(1) 同步刷盘：如上图所示，只有在消息真正持久化至磁盘后RocketMQ的Broker端才会真正返回给Producer端一个成功的ACK响应。同步刷盘对MQ消息可靠性来说是一种不错的保障，但是性能上会有较大影响，一般适用于金融业务应用该模式较多。</p><p>(2) 异步刷盘：能够充分利用OS的PageCache的优势，只要消息写入PageCache即可将成功的ACK返回给Producer端。消息刷盘采用后台异步线程提交的方式进行，降低了读写延迟，提高了MQ的性能和吞吐量。</p><h3 id="2-通信机制"><a href="#2-通信机制" class="headerlink" title="2 通信机制"></a>2 通信机制</h3><p>RocketMQ消息队列集群主要包括NameServe、Broker(Master&#x2F;Slave)、Producer、Consumer4个角色，基本通讯流程如下：</p><p>(1) Broker启动后需要完成一次将自己注册至NameServer的操作；随后每隔30s时间定时向NameServer上报Topic路由信息。</p><p>(2) 消息生产者Producer作为客户端发送消息时候，需要根据消息的Topic从本地缓存的TopicPublishInfoTable获取路由信息。如果没有则更新路由信息会从NameServer上重新拉取，同时Producer会默认每隔30s向NameServer拉取一次路由信息。</p><p>(3) 消息生产者Producer根据2）中获取的路由信息选择一个队列（MessageQueue）进行消息发送；Broker作为消息的接收者接收消息并落盘存储。</p><p>(4) 消息消费者Consumer根据2）中获取的路由信息，并再完成客户端的负载均衡后，选择其中的某一个或者某几个消息队列来拉取消息并进行消费。</p><p>从上面1）~3）中可以看出在消息生产者, Broker和NameServer之间都会发生通信（这里只说了MQ的部分通信），因此如何设计一个良好的网络通信模块在MQ中至关重要，它将决定RocketMQ集群整体的消息传输能力与最终的性能。</p><p>rocketmq-remoting 模块是 RocketMQ消息队列中负责网络通信的模块，它几乎被其他所有需要网络通信的模块（诸如rocketmq-client、rocketmq-broker、rocketmq-namesrv）所依赖和引用。为了实现客户端与服务器之间高效的数据请求与接收，RocketMQ消息队列自定义了通信协议并在Netty的基础之上扩展了通信模块。</p><h4 id="2-1-Remoting通信类结构"><a href="#2-1-Remoting通信类结构" class="headerlink" title="2.1 Remoting通信类结构"></a>2.1 Remoting通信类结构</h4><p><img src="/image/rocketmq_design_3.png"></p><h4 id="2-2-协议设计与编解码"><a href="#2-2-协议设计与编解码" class="headerlink" title="2.2 协议设计与编解码"></a>2.2 协议设计与编解码</h4><p>在Client和Server之间完成一次消息发送时，需要对发送的消息进行一个协议约定，因此就有必要自定义RocketMQ的消息协议。同时，为了高效地在网络中传输消息和对收到的消息读取，就需要对消息进行编解码。在RocketMQ中，RemotingCommand这个类在消息传输过程中对所有数据内容的封装，不但包含了所有的数据结构，还包含了编码解码操作。</p><table><thead><tr><th>Header字段</th><th>类型</th><th>Request说明</th><th>Response说明</th></tr></thead><tbody><tr><td>code</td><td>int</td><td>请求操作码，应答方根据不同的请求码进行不同的业务处理</td><td>应答响应码。0表示成功，非0则表示各种错误</td></tr><tr><td>language</td><td>LanguageCode</td><td>请求方实现的语言</td><td>应答方实现的语言</td></tr><tr><td>version</td><td>int</td><td>请求方程序的版本</td><td>应答方程序的版本</td></tr><tr><td>opaque</td><td>int</td><td>相当于reqeustId，在同一个连接上的不同请求标识码，与响应消息中的相对应</td><td>应答不做修改直接返回</td></tr><tr><td>flag</td><td>int</td><td>区分是普通RPC还是onewayRPC得标志</td><td>区分是普通RPC还是onewayRPC得标志</td></tr><tr><td>remark</td><td>String</td><td>传输自定义文本信息</td><td>传输自定义文本信息</td></tr><tr><td>extFields</td><td>HashMap&lt;String, String&gt;</td><td>请求自定义扩展信息</td><td>响应自定义扩展信息</td></tr></tbody></table><p><img src="/image/rocketmq_design_4.png"></p><p>可见传输内容主要可以分为以下4部分：</p><p>(1) 消息长度：总长度，四个字节存储，占用一个int类型；</p><p>(2) 序列化类型&amp;消息头长度：同样占用一个int类型，第一个字节表示序列化类型，后面三个字节表示消息头长度；</p><p>(3) 消息头数据：经过序列化后的消息头数据；</p><p>(4) 消息主体数据：消息主体的二进制字节数据内容；</p><h4 id="2-3-消息的通信方式和流程"><a href="#2-3-消息的通信方式和流程" class="headerlink" title="2.3 消息的通信方式和流程"></a>2.3 消息的通信方式和流程</h4><p>在RocketMQ消息队列中支持通信的方式主要有同步(sync)、异步(async)、单向(oneway)<br>三种。其中“单向”通信模式相对简单，一般用在发送心跳包场景下，无需关注其Response。这里，主要介绍RocketMQ的异步通信流程。</p><p><img src="/image/rocketmq_design_5.png"></p><h4 id="2-4-Reactor多线程设计"><a href="#2-4-Reactor多线程设计" class="headerlink" title="2.4 Reactor多线程设计"></a>2.4 Reactor多线程设计</h4><p>RocketMQ的RPC通信采用Netty组件作为底层通信库，同样也遵循了Reactor多线程模型，同时又在这之上做了一些扩展和优化。</p><p><img src="/image/rocketmq_design_6.png"></p><p>上面的框图中可以大致了解RocketMQ中NettyRemotingServer的Reactor 多线程模型。一个 Reactor 主线程（eventLoopGroupBoss，即为上面的1）负责监听 TCP网络连接请求，建立好连接，创建SocketChannel，并注册到selector上。RocketMQ的源码中会自动根据OS的类型选择NIO和Epoll，也可以通过参数配置）,然后监听真正的网络数据。拿到网络数据后，再丢给Worker线程池（eventLoopGroupSelector，即为上面的“N”，源码中默认设置为3），在真正执行业务逻辑之前需要进行SSL验证、编解码、空闲检查、网络连接管理，这些工作交给defaultEventExecutorGroup（即为上面的“M1”，源码中默认设置为8）去做。而处理业务操作放在业务线程池中执行，根据 RomotingCommand 的业务请求码code去processorTable这个本地缓存变量中找到对应的 processor，然后封装成task任务后，提交给对应的业务processor处理线程池来执行（sendMessageExecutor，以发送消息为例，即为上面的 “M2”）。从入口到业务逻辑的几个步骤中线程池一直再增加，这跟每一步逻辑复杂性相关，越复杂，需要的并发通道越宽。</p><table><thead><tr><th>线程数</th><th>线程名</th><th>线程具体说明</th></tr></thead><tbody><tr><td>1</td><td>NettyBoss_%d</td><td>Reactor 主线程</td></tr><tr><td>N</td><td>NettyServerEPOLLSelector_%d_%d</td><td>Reactor 线程池</td></tr><tr><td>M1</td><td>NettyServerCodecThread_%d</td><td>Worker线程池</td></tr><tr><td>M2</td><td>RemotingExecutorThread_%d</td><td>业务processor处理线程池</td></tr></tbody></table><h3 id="3-消息过滤"><a href="#3-消息过滤" class="headerlink" title="3 消息过滤"></a>3 消息过滤</h3><p>RocketMQ分布式消息队列的消息过滤方式有别于其它MQ中间件，是在Consumer端订阅消息时再做消息过滤的。RocketMQ这么做是还是在于其Producer端写入消息和Consomer端订阅消息采用分离存储的机制来实现的，Consumer端订阅消息是需要通过ConsumeQueue这个消息消费的逻辑队列拿到一个索引，然后再从CommitLog里面读取真正的消息实体内容，所以说到底也是还绕不开其存储结构。其ConsumeQueue的存储结构如下，可以看到其中有8个字节存储的Message Tag的哈希值，基于Tag的消息过滤正式基于这个字段值的。</p><p><img src="/image/rocketmq_design_7.png"></p><p>主要支持如下2种的过滤方式<br>(1) Tag过滤方式：Consumer端在订阅消息时除了指定Topic还可以指定TAG，如果一个消息有多个TAG，可以用||分隔。其中，Consumer端会将这个订阅请求构建成一个 SubscriptionData，发送一个Pull消息的请求给Broker端。Broker端从RocketMQ的文件存储层—Store读取数据之前，会用这些数据先构建一个MessageFilter，然后传给Store。Store从 ConsumeQueue读取到一条记录后，会用它记录的消息tag hash值去做过滤，由于在服务端只是根据hashcode进行判断，无法精确对tag原始字符串进行过滤，故在消息消费端拉取到消息后，还需要对消息的原始tag字符串进行比对，如果不同，则丢弃该消息，不进行消息消费。</p><p>(2) SQL92的过滤方式：这种方式的大致做法和上面的Tag过滤方式一样，只是在Store层的具体过滤过程不太一样，真正的 SQL expression 的构建和执行由rocketmq-filter模块负责的。每次过滤都去执行SQL表达式会影响效率，所以RocketMQ使用了BloomFilter避免了每次都去执行。SQL92的表达式上下文为消息的属性。</p><h3 id="4-负载均衡"><a href="#4-负载均衡" class="headerlink" title="4 负载均衡"></a>4 负载均衡</h3><p>RocketMQ中的负载均衡都在Client端完成，具体来说的话，主要可以分为Producer端发送消息时候的负载均衡和Consumer端订阅消息的负载均衡。</p><h4 id="4-1-Producer的负载均衡"><a href="#4-1-Producer的负载均衡" class="headerlink" title="4.1 Producer的负载均衡"></a>4.1 Producer的负载均衡</h4><p>Producer端在发送消息的时候，会先根据Topic找到指定的TopicPublishInfo，在获取了TopicPublishInfo路由信息后，RocketMQ的客户端在默认方式下selectOneMessageQueue()方法会从TopicPublishInfo中的messageQueueList中选择一个队列（MessageQueue）进行发送消息。具体的容错策略均在MQFaultStrategy这个类中定义。这里有一个sendLatencyFaultEnable开关变量，如果开启，在随机递增取模的基础上，再过滤掉not available的Broker代理。所谓的”latencyFaultTolerance”，是指对之前失败的，按一定的时间做退避。例如，如果上次请求的latency超过550Lms，就退避3000Lms；超过1000L，就退避60000L；如果关闭，采用随机递增取模的方式选择一个队列（MessageQueue）来发送消息，latencyFaultTolerance机制是实现消息发送高可用的核心关键所在。</p><h4 id="4-2-Consumer的负载均衡"><a href="#4-2-Consumer的负载均衡" class="headerlink" title="4.2 Consumer的负载均衡"></a>4.2 Consumer的负载均衡</h4><p>在RocketMQ中，Consumer端的两种消费模式（Push&#x2F;Pull）都是基于拉模式来获取消息的，而在Push模式只是对pull模式的一种封装，其本质实现为消息拉取线程在从服务器拉取到一批消息后，然后提交到消息消费线程池后，又“马不停蹄”的继续向服务器再次尝试拉取消息。如果未拉取到消息，则延迟一下又继续拉取。在两种基于拉模式的消费方式（Push&#x2F;Pull）中，均需要Consumer端在知道从Broker端的哪一个消息队列—队列中去获取消息。因此，有必要在Consumer端来做负载均衡，即Broker端中多个MessageQueue分配给同一个ConsumerGroup中的哪些Consumer消费。</p><p>1、Consumer端的心跳包发送</p><p>在Consumer启动后，它就会通过定时任务不断地向RocketMQ集群中的所有Broker实例发送心跳包（其中包含了，消息消费分组名称、订阅关系集合、消息通信模式和客户端id的值等信息）。Broker端在收到Consumer的心跳消息后，会将它维护在ConsumerManager的本地缓存变量—consumerTable，同时并将封装后的客户端网络通道信息保存在本地缓存变量—channelInfoTable中，为之后做Consumer端的负载均衡提供可以依据的元数据信息。</p><p>2、Consumer端实现负载均衡的核心类—RebalanceImpl</p><p>在Consumer实例的启动流程中的启动MQClientInstance实例部分，会完成负载均衡服务线程—RebalanceService的启动（每隔20s执行一次）。通过查看源码可以发现，RebalanceService线程的run()方法最终调用的是RebalanceImpl类的rebalanceByTopic()方法，该方法是实现Consumer端负载均衡的核心。这里，rebalanceByTopic()方法会根据消费者通信类型为“广播模式”还是“集群模式”做不同的逻辑处理。这里主要来看下集群模式下的主要处理流程：</p><p>(1) 从rebalanceImpl实例的本地缓存变量—topicSubscribeInfoTable中，获取该Topic主题下的消息消费队列集合（mqSet）；</p><p>(2) 根据topic和consumerGroup为参数调用mQClientFactory.findConsumerIdList()方法向Broker端发送获取该消费组下消费者Id列表的RPC通信请求（Broker端基于前面Consumer端上报的心跳包数据而构建的consumerTable做出响应返回，业务请求码：GET_CONSUMER_LIST_BY_GROUP）；</p><p>(3) 先对Topic下的消息消费队列、消费者Id排序，然后用消息队列分配策略算法（默认为：消息队列的平均分配算法），计算出待拉取的消息队列。这里的平均分配算法，类似于分页的算法，将所有MessageQueue排好序类似于记录，将所有消费端Consumer排好序类似页数，并求出每一页需要包含的平均size和每个页面记录的范围range，最后遍历整个range而计算出当前Consumer端应该分配到的记录（这里即为：MessageQueue）。</p><p><img src="/image/rocketmq_design_8.png"></p><p>(4) 然后，调用updateProcessQueueTableInRebalance()方法，具体的做法是，先将分配到的消息队列集合（mqSet）与processQueueTable做一个过滤比对。</p><p><img src="/image/rocketmq_design_9.png"></p><ul><li><p>上图中processQueueTable标注的红色部分，表示与分配到的消息队列集合mqSet互不包含。将这些队列设置Dropped属性为true，然后查看这些队列是否可以移除出processQueueTable缓存变量，这里具体执行removeUnnecessaryMessageQueue()方法，即每隔1s 查看是否可以获取当前消费处理队列的锁，拿到的话返回true。如果等待1s后，仍然拿不到当前消费处理队列的锁则返回false。如果返回true，则从processQueueTable缓存变量中移除对应的Entry；</p></li><li><p>上图中processQueueTable的绿色部分，表示与分配到的消息队列集合mqSet的交集。判断该ProcessQueue是否已经过期了，在Pull模式的不用管，如果是Push模式的，设置Dropped属性为true，并且调用removeUnnecessaryMessageQueue()方法，像上面一样尝试移除Entry；</p></li></ul><p>最后，为过滤后的消息队列集合（mqSet）中的每个MessageQueue创建一个ProcessQueue对象并存入RebalanceImpl的processQueueTable队列中（其中调用RebalanceImpl实例的computePullFromWhere(MessageQueue mq)方法获取该MessageQueue对象的下一个进度消费值offset，随后填充至接下来要创建的pullRequest对象属性中），并创建拉取请求对象—pullRequest添加到拉取列表—pullRequestList中，最后执行dispatchPullRequest()方法，将Pull消息的请求对象PullRequest依次放入PullMessageService服务线程的阻塞队列pullRequestQueue中，待该服务线程取出后向Broker端发起Pull消息的请求。其中，可以重点对比下，RebalancePushImpl和RebalancePullImpl两个实现类的dispatchPullRequest()方法不同，RebalancePullImpl类里面的该方法为空，这样子也就回答了上一篇中最后的那道思考题了。</p><p>消息消费队列在同一消费组不同消费者之间的负载均衡，其核心设计理念是在一个消息消费队列在同一时间只允许被同一消费组内的一个消费者消费，一个消息消费者能同时消费多个消息队列。</p><h3 id="5-事务消息"><a href="#5-事务消息" class="headerlink" title="5 事务消息"></a>5 事务消息</h3><p>Apache RocketMQ在4.3.0版中已经支持分布式事务消息，这里RocketMQ采用了2PC的思想来实现了提交事务消息，同时增加一个补偿逻辑来处理二阶段超时或者失败的消息，如下图所示。</p><p><img src="/image/rocketmq_design_10.png"></p><h4 id="5-1-RocketMQ事务消息流程概要"><a href="#5-1-RocketMQ事务消息流程概要" class="headerlink" title="5.1 RocketMQ事务消息流程概要"></a>5.1 RocketMQ事务消息流程概要</h4><p>上图说明了事务消息的大致方案，其中分为两个流程：正常事务消息的发送及提交、事务消息的补偿流程。</p><p>1.事务消息发送及提交：</p><p>(1) 发送消息（half消息）。</p><p>(2) 服务端响应消息写入结果。</p><p>(3) 根据发送结果执行本地事务（如果写入失败，此时half消息对业务不可见，本地逻辑不执行）。</p><p>(4) 根据本地事务状态执行Commit或者Rollback（Commit操作生成消息索引，消息对消费者可见）</p><p>2.补偿流程：</p><p>(1) 对没有Commit&#x2F;Rollback的事务消息（pending状态的消息），从服务端发起一次“回查”</p><p>(2) Producer收到回查消息，检查回查消息对应的本地事务的状态</p><p>(3) 根据本地事务状态，重新Commit或者Rollback</p><p>其中，补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。</p><h4 id="5-2-RocketMQ事务消息设计"><a href="#5-2-RocketMQ事务消息设计" class="headerlink" title="5.2 RocketMQ事务消息设计"></a>5.2 RocketMQ事务消息设计</h4><p>1.事务消息在一阶段对用户不可见</p><p>在RocketMQ事务消息的主要流程中，一阶段的消息如何对用户不可见。其中，事务消息相对普通消息最大的特点就是一阶段发送的消息对用户是不可见的。那么，如何做到写入消息但是对用户不可见呢？RocketMQ事务消息的做法是：如果消息是half消息，将备份原消息的主题与消息消费队列，然后改变主题为RMQ_SYS_TRANS_HALF_TOPIC。由于消费组未订阅该主题，故消费端无法消费half类型的消息，然后RocketMQ会开启一个定时任务，从Topic为RMQ_SYS_TRANS_HALF_TOPIC中拉取消息进行消费，根据生产者组获取一个服务提供者发送回查事务状态请求，根据事务状态来决定是提交或回滚消息。</p><p>在RocketMQ中，消息在服务端的存储结构如下，每条消息都会有对应的索引信息，Consumer通过ConsumeQueue这个二级索引来读取消息实体内容，其流程如下：</p><p><img src="/image/rocketmq_design_11.png"></p><p>RocketMQ的具体实现策略是：写入的如果事务消息，对消息的Topic和Queue等属性进行替换，同时将原来的Topic和Queue信息存储到消息的属性中，正因为消息主题被替换，故消息并不会转发到该原主题的消息消费队列，消费者无法感知消息的存在，不会消费。其实改变消息主题是RocketMQ的常用“套路”，回想一下延时消息的实现机制。</p><p>2.Commit和Rollback操作以及Op消息的引入</p><p>在完成一阶段写入一条对用户不可见的消息后，二阶段如果是Commit操作，则需要让消息对用户可见；如果是Rollback则需要撤销一阶段的消息。先说Rollback的情况。对于Rollback，本身一阶段的消息对用户是不可见的，其实不需要真正撤销消息（实际上RocketMQ也无法去真正的删除一条消息，因为是顺序写文件的）。但是区别于这条消息没有确定状态（Pending状态，事务悬而未决），需要一个操作来标识这条消息的最终状态。RocketMQ事务消息方案中引入了Op消息的概念，用Op消息标识事务消息已经确定的状态（Commit或者Rollback）。如果一条事务消息没有对应的Op消息，说明这个事务的状态还无法确定（可能是二阶段失败了）。引入Op消息后，事务消息无论是Commit或者Rollback都会记录一个Op操作。Commit相对于Rollback只是在写入Op消息前创建Half消息的索引。</p><p>3.Op消息的存储和对应关系</p><p>RocketMQ将Op消息写入到全局一个特定的Topic中通过源码中的方法—TransactionalMessageUtil.buildOpTopic()；这个Topic是一个内部的Topic（像Half消息的Topic一样），不会被用户消费。Op消息的内容为对应的Half消息的存储的Offset，这样通过Op消息能索引到Half消息进行后续的回查操作。</p><p><img src="/image/rocketmq_design_12.png"></p><p>4.Half消息的索引构建</p><p>在执行二阶段Commit操作时，需要构建出Half消息的索引。一阶段的Half消息由于是写到一个特殊的Topic，所以二阶段构建索引时需要读取出Half消息，并将Topic和Queue替换成真正的目标的Topic和Queue，之后通过一次普通消息的写入操作来生成一条对用户可见的消息。所以RocketMQ事务消息二阶段其实是利用了一阶段存储的消息的内容，在二阶段时恢复出一条完整的普通消息，然后走一遍消息写入流程。</p><p>5.如何处理二阶段失败的消息？</p><p>如果在RocketMQ事务消息的二阶段过程中失败了，例如在做Commit操作时，出现网络问题导致Commit失败，那么需要通过一定的策略使这条消息最终被Commit。RocketMQ采用了一种补偿机制，称为“回查”。Broker端对未确定状态的消息发起回查，将消息发送到对应的Producer端（同一个Group的Producer），由Producer根据消息来检查本地事务的状态，进而执行Commit或者Rollback。Broker端通过对比Half消息和Op消息进行事务消息的回查并且推进CheckPoint（记录那些事务消息的状态是确定的）。</p><p>值得注意的是，rocketmq并不会无休止的的信息事务状态回查，默认回查15次，如果15次回查还是无法得知事务状态，rocketmq默认回滚该消息。</p><h3 id="6-消息查询"><a href="#6-消息查询" class="headerlink" title="6 消息查询"></a>6 消息查询</h3><p>RocketMQ支持按照下面两种维度（“按照Message Id查询消息”、“按照Message Key查询消息”）进行消息查询。</p><h4 id="6-1-按照MessageId查询消息"><a href="#6-1-按照MessageId查询消息" class="headerlink" title="6.1   按照MessageId查询消息"></a>6.1   按照MessageId查询消息</h4><p>RocketMQ中的MessageId的长度总共有16字节，其中包含了消息存储主机地址（IP地址和端口），消息Commit Log offset。“按照MessageId查询消息”在RocketMQ中具体做法是：Client端从MessageId中解析出Broker的地址（IP地址和端口）和Commit Log的偏移地址后封装成一个RPC请求后通过Remoting通信层发送（业务请求码：VIEW_MESSAGE_BY_ID）。Broker端走的是QueryMessageProcessor，读取消息的过程用其中的 commitLog offset 和 size 去 commitLog 中找到真正的记录并解析成一个完整的消息返回。</p><h4 id="6-2-按照Message-Key查询消息"><a href="#6-2-按照Message-Key查询消息" class="headerlink" title="6.2  按照Message Key查询消息"></a>6.2  按照Message Key查询消息</h4><p>“按照Message Key查询消息”，主要是基于RocketMQ的IndexFile索引文件来实现的。RocketMQ的索引文件逻辑结构，类似JDK中HashMap的实现。索引文件的具体结构如下：</p><p><img src="/image/rocketmq_design_13.png"></p><p>IndexFile索引文件为用户提供通过“按照Message Key查询消息”的消息索引查询服务，IndexFile文件的存储位置是：$HOME\store\index${fileName}，文件名fileName是以创建时的时间戳命名的，文件大小是固定的，等于40+500W*4+2000W*20&#x3D; 420000040个字节大小。如果消息的properties中设置了UNIQ_KEY这个属性，就用 topic + “#” + UNIQ_KEY的value作为 key 来做写入操作。如果消息设置了KEYS属性（多个KEY以空格分隔），也会用 topic + “#” + KEY 来做索引。</p><p>其中的索引数据包含了Key Hash&#x2F;CommitLog Offset&#x2F;Timestamp&#x2F;NextIndex offset 这四个字段，一共20 Byte。NextIndex offset 即前面读出来的 slotValue，如果有 hash冲突，就可以用这个字段将所有冲突的索引用链表的方式串起来了。Timestamp记录的是消息storeTimestamp之间的差，并不是一个绝对的时间。整个Index File的结构如图，40 Byte 的Header用于保存一些总的统计信息，4*500W的 Slot Table并不保存真正的索引数据，而是保存每个槽位对应的单向链表的头。20*2000W 是真正的索引数据，即一个 Index File 可以保存 2000W个索引。</p><p>“按照Message Key查询消息”的方式，RocketMQ的具体做法是，主要通过Broker端的QueryMessageProcessor业务处理器来查询，读取消息的过程就是用topic和key找到IndexFile索引文件中的一条记录，根据其中的commitLog offset从CommitLog文件中读取消息的实体内容。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;设计-design&quot;&gt;&lt;a href=&quot;#设计-design&quot; class=&quot;headerlink&quot; title=&quot;设计(design)&quot;&gt;&lt;/a&gt;设计(design)&lt;/h1&gt;&lt;hr&gt;
&lt;h3 id=&quot;1-消息存储&quot;&gt;&lt;a href=&quot;#1-消息存储&quot; clas</summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="设计(design)" scheme="https://nydia.github.io/tags/%E8%AE%BE%E8%AE%A1-design/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ Deployment Architectures and Setup Steps</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/en/Deployment/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/en/Deployment/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T13:00:16.600Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Deployment-Architectures-and-Setup-Steps"><a href="#Deployment-Architectures-and-Setup-Steps" class="headerlink" title="Deployment Architectures and Setup Steps"></a>Deployment Architectures and Setup Steps</h1><h2 id="Cluster-Setup"><a href="#Cluster-Setup" class="headerlink" title="Cluster Setup"></a>Cluster Setup</h2><h3 id="1-Single-Master-mode"><a href="#1-Single-Master-mode" class="headerlink" title="1 Single Master mode"></a>1 Single Master mode</h3><p>This is the simplest, but also the riskiest mode, that makes the entire service unavailable once the broker restarts or goes down. Production environments are not recommended, but can be used for local testing and development. Here are the steps to build.</p><p><strong>1）Start NameServer</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Start Name Server first</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqnamesrv &amp;</span></span><br><span class="line"><span class="meta prompt_"> </span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Then verify that the Name Server starts successfully</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">tail</span> -f ~/logs/rocketmqlogs/namesrv.log</span></span><br><span class="line">The Name Server boot success...</span><br></pre></td></tr></table></figure><p>We can see ‘The Name Server boot success.. ‘ in namesrv.log that indicates the NameServer has been started successfully.</p><p><strong>2）Start Broker</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Also start broker first</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh bin/mqbroker -n localhost:9876 &amp;</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Then verify that the broker is started successfully, for example, the IP of broker is 192.168.1.2 and the name is broker-a</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">tail</span> -f ~/logs/rocketmqlogs/Broker.<span class="built_in">log</span></span> </span><br><span class="line">The broker[broker-a,192.169.1.2:10911] boot success...</span><br></pre></td></tr></table></figure><p>We can see ‘The broker[brokerName,ip:port] boot success..’ in Broker.log that indicates the broker has been started successfully.</p><h3 id="2-Multiple-Master-mode"><a href="#2-Multiple-Master-mode" class="headerlink" title="2 Multiple Master mode"></a>2 Multiple Master mode</h3><p>Multiple master mode means a mode with all master nodes(such as 2 or 3 master nodes) and no slave node. The advantages and disadvantages of this mode are as follows:</p><ul><li>Advantages: <ol><li>Simple configuration.</li><li>Outage or restart(for maintenance) of one master node has no impact on the application. </li><li>When the disk is configured as RAID10, messages are not lost because the RAID10 disk is very reliable, even if the machine is not recoverable (In the case of asynchronous flush disk mode of the message, a small number of messages are lost; If the brush mode of a message is synchronous, no message will be lost).</li><li>In this mode, the performance is the highest.</li></ol></li><li>Disadvantages:<ol><li>During a single machine outage, messages that are not consumed on this machine are not subscribed to until the machine recovers, and message real-time is affected.</li></ol></li></ul><p>The starting steps for multiple master mode are as follows:</p><p><strong>1）Start NameServer</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Start Name Server first</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqnamesrv &amp;</span></span><br><span class="line"><span class="meta prompt_"> </span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Then verify that the Name Server starts successfully</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">tail</span> -f ~/logs/rocketmqlogs/namesrv.log</span></span><br><span class="line">The Name Server boot success...</span><br></pre></td></tr></table></figure><p><strong>2）Start the Broker cluster</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## For example, starting the first Master on machine A, assuming that the configured NameServer IP is: 192.168.1.1.</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-noslave/broker-a.properties &amp;</span></span><br><span class="line"><span class="meta prompt_"> </span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Then starting the second Master on machine B, assuming that the configured NameServer IP is: 192.168.1.1.</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-noslave/broker-b.properties &amp;</span></span><br><span class="line"></span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>The boot command shown above is used in the case of a single NameServer.For clusters of multiple NameServer, the address list after the -n argument in the broker boot command is separated by semicolons, for example, 192.168.1.1: 9876;192.161.2: 9876.</p><h3 id="3-Multiple-Master-And-Multiple-Slave-Mode-Asynchronous-replication"><a href="#3-Multiple-Master-And-Multiple-Slave-Mode-Asynchronous-replication" class="headerlink" title="3 Multiple Master And Multiple Slave Mode-Asynchronous replication"></a>3 Multiple Master And Multiple Slave Mode-Asynchronous replication</h3><p>Each master node configures more thran one slave nodes, with multiple pairs of master-slave.HA uses asynchronous replication, with a short message delay (millisecond) between master node and slave node.The advantages and disadvantages of this mode are as follows:</p><ul><li>Advantages: <ol><li>Even if the disk is corrupted, very few messages will be lost and the real-time performance of the message will not be affected.</li><li>At the same time, when master node is down, consumers can still consume messages from slave node, and the process is transparent to the application itself and does not require human intervention.</li><li>Performance is almost as high as multiple master mode.</li></ol></li><li>Disadvantages:<ol><li>A small number of messages will be lost when master node is down and the disk is corrupted.</li></ol></li></ul><p>The starting steps for multiple master and multiple slave mode are as follows:</p><p><strong>1）Start NameServer</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Start Name Server first</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqnamesrv &amp;</span></span><br><span class="line"><span class="meta prompt_"> </span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Then verify that the Name Server starts successfully</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">tail</span> -f ~/logs/rocketmqlogs/namesrv.log</span></span><br><span class="line">The Name Server boot success...</span><br></pre></td></tr></table></figure><p><strong>2）Start the Broker cluster</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## For example, starting the first Master on machine A, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876.</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-async/broker-a.properties &amp;</span></span><br><span class="line"><span class="meta prompt_"> </span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Then starting the second Master on machine B, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876.</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-async/broker-b.properties &amp;</span></span><br><span class="line"><span class="meta prompt_"> </span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Then starting the first Slave on machine C, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876.</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-async/broker-a-s.properties &amp;</span></span><br><span class="line"><span class="meta prompt_"> </span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Last starting the second Slave on machine D, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876.</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-async/broker-b-s.properties &amp;</span></span><br></pre></td></tr></table></figure><p>The above shows a startup command for 2M-2S-Async mode, similar to other nM-nS-Async modes.</p><h3 id="4-Multiple-Master-And-Multiple-Slave-Mode-Synchronous-dual-write"><a href="#4-Multiple-Master-And-Multiple-Slave-Mode-Synchronous-dual-write" class="headerlink" title="4 Multiple Master And Multiple Slave Mode-Synchronous dual write"></a>4 Multiple Master And Multiple Slave Mode-Synchronous dual write</h3><p>In this mode, multiple slave node are configured for each master node and there are multiple pairs of Master-Slave.HA uses synchronous double-write, that is, the success response will be returned to the application only when the message is successfully written into the master node and replicated to more than one slave node.</p><p>The advantages and disadvantages of this model are as follows:</p><ul><li>Advantages: <ol><li>Neither the data nor the service has a single point of failure. </li><li>In the case of master node shutdown, the message is also undelayed. </li><li>Service availability and data availability are very high;</li></ol></li><li>Disadvantages:<ol><li>The performance in this mode is slightly lower than in asynchronous replication mode (about 10% lower).</li><li>The RT sending a single message is slightly higher, and the current version, the slave node cannot automatically switch to the master after the master node is down.</li></ol></li></ul><p>The starting steps are as follows:</p><p><strong>1）Start NameServer</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Start Name Server first</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqnamesrv &amp;</span></span><br><span class="line"><span class="meta prompt_"> </span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Then verify that the Name Server starts successfully</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">tail</span> -f ~/logs/rocketmqlogs/namesrv.log</span></span><br><span class="line">The Name Server boot success...</span><br></pre></td></tr></table></figure><p><strong>2）Start the Broker cluster</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## For example, starting the first Master on machine A, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876.</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-<span class="built_in">sync</span>/broker-a.properties &amp;</span></span><br><span class="line"><span class="meta prompt_"> </span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Then starting the second Master on machine B, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876.</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-<span class="built_in">sync</span>/broker-b.properties &amp;</span></span><br><span class="line"><span class="meta prompt_"> </span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Then starting the first Slave on machine C, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876.</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-<span class="built_in">sync</span>/broker-a-s.properties &amp;</span></span><br><span class="line"><span class="meta prompt_"> </span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## Last starting the second Slave on machine D, assuming that the configured NameServer IP is: 192.168.1.1 and port is 9876.</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-<span class="built_in">sync</span>/broker-b-s.properties &amp;</span></span><br></pre></td></tr></table></figure><p>The above Master and Slave are paired by specifying the same config named “brokerName”, the “brokerId” of the master node must be 0, and the “brokerId” of the slave node must be greater than 0.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Deployment-Architectures-and-Setup-Steps&quot;&gt;&lt;a href=&quot;#Deployment-Architectures-and-Setup-Steps&quot; class=&quot;headerlink&quot; title=&quot;Deployment A</summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="Deployment Architectures and Setup Steps" scheme="https://nydia.github.io/tags/Deployment-Architectures-and-Setup-Steps/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ运维管理</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/cn/operation/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/cn/operation/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T12:57:52.142Z</updated>
    
    <content type="html"><![CDATA[<h1 id="运维管理"><a href="#运维管理" class="headerlink" title="运维管理"></a>运维管理</h1><hr><h3 id="1-集群搭建"><a href="#1-集群搭建" class="headerlink" title="1   集群搭建"></a>1   集群搭建</h3><h4 id="1-1-单Master模式"><a href="#1-1-单Master模式" class="headerlink" title="1.1 单Master模式"></a>1.1 单Master模式</h4><p>这种方式风险较大，一旦Broker重启或者宕机时，会导致整个服务不可用。不建议线上环境使用,可以用于本地测试。</p><h5 id="1）启动-NameServer"><a href="#1）启动-NameServer" class="headerlink" title="1）启动 NameServer"></a>1）启动 NameServer</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">### 首先启动Name Server</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqnamesrv &amp;</span><br><span class="line"> </span><br><span class="line"><span class="comment">### 验证Name Server 是否启动成功</span></span><br><span class="line">$ <span class="built_in">tail</span> -f ~/logs/rocketmqlogs/namesrv.log</span><br><span class="line">The Name Server boot success...</span><br></pre></td></tr></table></figure><h5 id="2）启动-Broker"><a href="#2）启动-Broker" class="headerlink" title="2）启动 Broker"></a>2）启动 Broker</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">### 启动Broker</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh bin/mqbroker -n localhost:9876 &amp;</span><br><span class="line"></span><br><span class="line"><span class="comment">### 验证Name Server 是否启动成功，例如Broker的IP为：192.168.1.2，且名称为broker-a</span></span><br><span class="line">$ <span class="built_in">tail</span> -f ~/logs/rocketmqlogs/Broker.<span class="built_in">log</span> </span><br><span class="line">The broker[broker-a, 192.169.1.2:10911] boot success...</span><br></pre></td></tr></table></figure><h4 id="1-2-多Master模式"><a href="#1-2-多Master模式" class="headerlink" title="1.2 多Master模式"></a>1.2 多Master模式</h4><p>一个集群无Slave，全是Master，例如2个Master或者3个Master，这种模式的优缺点如下：</p><ul><li><p>优点：配置简单，单个Master宕机或重启维护对应用无影响，在磁盘配置为RAID10时，即使机器宕机不可恢复情况下，由于RAID10磁盘非常可靠，消息也不会丢（异步刷盘丢失少量消息，同步刷盘一条不丢），性能最高；</p></li><li><p>缺点：单台机器宕机期间，这台机器上未被消费的消息在机器恢复之前不可订阅，消息实时性会受到影响。</p></li></ul><h5 id="1）启动NameServer"><a href="#1）启动NameServer" class="headerlink" title="1）启动NameServer"></a>1）启动NameServer</h5><p>NameServer需要先于Broker启动，且如果在生产环境使用，为了保证高可用，建议一般规模的集群启动3个NameServer，各节点的启动命令相同，如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">### 首先启动Name Server</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqnamesrv &amp;</span><br><span class="line"> </span><br><span class="line"><span class="comment">### 验证Name Server 是否启动成功</span></span><br><span class="line">$ <span class="built_in">tail</span> -f ~/logs/rocketmqlogs/namesrv.log</span><br><span class="line">The Name Server boot success...</span><br></pre></td></tr></table></figure><h5 id="2）启动Broker集群"><a href="#2）启动Broker集群" class="headerlink" title="2）启动Broker集群"></a>2）启动Broker集群</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">### 在机器A，启动第一个Master，例如NameServer的IP为：192.168.1.1</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-noslave/broker-a.properties &amp;</span><br><span class="line"> </span><br><span class="line"><span class="comment">### 在机器B，启动第二个Master，例如NameServer的IP为：192.168.1.1</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-noslave/broker-b.properties &amp;</span><br><span class="line"></span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>如上启动命令是在单个NameServer情况下使用的。对于多个NameServer的集群，Broker启动命令中<code>-n</code>后面的地址列表用分号隔开即可，例如 <code>192.168.1.1:9876;192.161.2:9876</code>。</p><h4 id="1-3-多Master多Slave模式-异步复制"><a href="#1-3-多Master多Slave模式-异步复制" class="headerlink" title="1.3 多Master多Slave模式-异步复制"></a>1.3 多Master多Slave模式-异步复制</h4><p>每个Master配置一个Slave，有多对Master-Slave，HA采用异步复制方式，主备有短暂消息延迟（毫秒级），这种模式的优缺点如下：</p><ul><li><p>优点：即使磁盘损坏，消息丢失的非常少，且消息实时性不会受影响，同时Master宕机后，消费者仍然可以从Slave消费，而且此过程对应用透明，不需要人工干预，性能同多Master模式几乎一样；</p></li><li><p>缺点：Master宕机，磁盘损坏情况下会丢失少量消息。</p></li></ul><h5 id="1）启动NameServer-1"><a href="#1）启动NameServer-1" class="headerlink" title="1）启动NameServer"></a>1）启动NameServer</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">### 首先启动Name Server</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqnamesrv &amp;</span><br><span class="line"> </span><br><span class="line"><span class="comment">### 验证Name Server 是否启动成功</span></span><br><span class="line">$ <span class="built_in">tail</span> -f ~/logs/rocketmqlogs/namesrv.log</span><br><span class="line">The Name Server boot success...</span><br></pre></td></tr></table></figure><h5 id="2）启动Broker集群-1"><a href="#2）启动Broker集群-1" class="headerlink" title="2）启动Broker集群"></a>2）启动Broker集群</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">### 在机器A，启动第一个Master，例如NameServer的IP为：192.168.1.1</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-async/broker-a.properties &amp;</span><br><span class="line"> </span><br><span class="line"><span class="comment">### 在机器B，启动第二个Master，例如NameServer的IP为：192.168.1.1</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-async/broker-b.properties &amp;</span><br><span class="line"> </span><br><span class="line"><span class="comment">### 在机器C，启动第一个Slave，例如NameServer的IP为：192.168.1.1</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-async/broker-a-s.properties &amp;</span><br><span class="line"> </span><br><span class="line"><span class="comment">### 在机器D，启动第二个Slave，例如NameServer的IP为：192.168.1.1</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-async/broker-b-s.properties &amp;</span><br></pre></td></tr></table></figure><h4 id="1-4-多Master多Slave模式-同步双写"><a href="#1-4-多Master多Slave模式-同步双写" class="headerlink" title="1.4 多Master多Slave模式-同步双写"></a>1.4 多Master多Slave模式-同步双写</h4><p>每个Master配置一个Slave，有多对Master-Slave，HA采用同步双写方式，即只有主备都写成功，才向应用返回成功，这种模式的优缺点如下：</p><ul><li><p>优点：数据与服务都无单点故障，Master宕机情况下，消息无延迟，服务可用性与数据可用性都非常高；</p></li><li><p>缺点：性能比异步复制模式略低（大约低10%左右），发送单个消息的RT会略高，且目前版本在主节点宕机后，备机不能自动切换为主机。</p></li></ul><h5 id="1）启动NameServer-2"><a href="#1）启动NameServer-2" class="headerlink" title="1）启动NameServer"></a>1）启动NameServer</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">### 首先启动Name Server</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqnamesrv &amp;</span><br><span class="line"> </span><br><span class="line"><span class="comment">### 验证Name Server 是否启动成功</span></span><br><span class="line">$ <span class="built_in">tail</span> -f ~/logs/rocketmqlogs/namesrv.log</span><br><span class="line">The Name Server boot success...</span><br></pre></td></tr></table></figure><h5 id="2）启动Broker集群-2"><a href="#2）启动Broker集群-2" class="headerlink" title="2）启动Broker集群"></a>2）启动Broker集群</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">### 在机器A，启动第一个Master，例如NameServer的IP为：192.168.1.1</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-<span class="built_in">sync</span>/broker-a.properties &amp;</span><br><span class="line"> </span><br><span class="line"><span class="comment">### 在机器B，启动第二个Master，例如NameServer的IP为：192.168.1.1</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-<span class="built_in">sync</span>/broker-b.properties &amp;</span><br><span class="line"> </span><br><span class="line"><span class="comment">### 在机器C，启动第一个Slave，例如NameServer的IP为：192.168.1.1</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-<span class="built_in">sync</span>/broker-a-s.properties &amp;</span><br><span class="line"> </span><br><span class="line"><span class="comment">### 在机器D，启动第二个Slave，例如NameServer的IP为：192.168.1.1</span></span><br><span class="line">$ <span class="built_in">nohup</span> sh mqbroker -n 192.168.1.1:9876 -c <span class="variable">$ROCKETMQ_HOME</span>/conf/2m-2s-<span class="built_in">sync</span>/broker-b-s.properties &amp;</span><br></pre></td></tr></table></figure><p>以上Broker与Slave配对是通过指定相同的BrokerName参数来配对，Master的BrokerId必须是0，Slave的BrokerId必须是大于0的数。另外一个Master下面可以挂载多个Slave，同一Master下的多个Slave通过指定不同的BrokerId来区分。$ROCKETMQ_HOME指的RocketMQ安装目录，需要用户自己设置此环境变量。</p><h3 id="2-mqadmin管理工具"><a href="#2-mqadmin管理工具" class="headerlink" title="2 mqadmin管理工具"></a>2 mqadmin管理工具</h3><blockquote><p>注意：</p><ol><li>执行命令方法：<code>./mqadmin &#123;command&#125; &#123;args&#125;</code></li><li>几乎所有命令都需要配置-n表示NameServer地址，格式为ip:port</li><li>几乎所有命令都可以通过-h获取帮助</li><li>如果既有Broker地址（-b）配置项又有clusterName（-c）配置项，则优先以Broker地址执行命令，如果不配置Broker地址，则对集群中所有主机执行命令，只支持一个Broker地址。-b格式为ip:port，port默认是10911</li><li>在tools下可以看到很多命令，但并不是所有命令都能使用，只有在MQAdminStartup中初始化的命令才能使用，你也可以修改这个类，增加或自定义命令</li><li>由于版本更新问题，少部分命令可能未及时更新，遇到错误请直接阅读相关命令源码</li></ol></blockquote><h4 id="2-1-Topic相关"><a href="#2-1-Topic相关" class="headerlink" title="2.1 Topic相关"></a>2.1 Topic相关</h4><table border=0 cellpadding=0 cellspacing=0 width=714> <col width=177> <col width=175> <col width=177> <col width=185> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl63 width=177 style='height:17.0pt;width:133pt'>名称</td>  <td class=xl64 width=175 style='width:131pt'>含义</td>  <td class=xl64 width=177 style='width:133pt'>命令选项</td>  <td class=xl64 width=185 style='width:139pt'>说明</td> </tr> <tr height=132 style='height:99.0pt'>  <td rowspan=8 height=593 class=xl68 width=163 style='border-bottom:1.0pt;  height:444.0pt;border-top:none;width:122pt'>updateTopic</td>  <td rowspan=8 class=xl70 width=135 style='border-bottom:1.0pt;  border-top:none;width:101pt'>创建更新Topic配置</td>  <td class=xl65 width=149 style='width:112pt'>-b</td>  <td class=xl66 width=159 style='width:119pt'>Broker 地址，表示 topic 所在  Broker，只支持单台Broker，地址为ip:port</td> </tr> <tr height=132 style='height:99.0pt'>  <td height=132 class=xl65 width=149 style='height:99.0pt;width:112pt'>-c</td>  <td class=xl66 width=159 style='width:119pt'>cluster 名称，表示 topic 所在集群（集群可通过  clusterList 查询）</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl65 width=149 style='height:17.0pt;width:112pt'>-h-</td>  <td class=xl66 width=159 style='width:119pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=149 style='height:43.0pt;width:112pt'>-n</td>  <td class=xl66 width=159 style='width:119pt'>NameServer服务地址，格式 ip:port</td> </tr> <tr height=76 style='height:57.0pt'>  <td height=76 class=xl65 width=149 style='height:57.0pt;width:112pt'>-p</td>  <td class=xl66 width=159 style='width:119pt'>指定新topic的读写权限( W=2|R=4|WR=6 )</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl65 width=149 style='height:29.0pt;width:112pt'>-r</td>  <td class=xl66 width=159 style='width:119pt'>可读队列数（默认为 8）</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl65 width=149 style='height:29.0pt;width:112pt'>-w</td>  <td class=xl66 width=159 style='width:119pt'>可写队列数（默认为 8）</td> </tr> <tr height=95 style='height:71.0pt'>  <td height=95 class=xl65 width=149 style='height:71.0pt;width:112pt'>-t</td>  <td class=xl66 width=159 style='width:119pt'>topic 名称（名称只能使用字符  ^[a-zA-Z0-9_-]+$ ）</td> </tr> <tr height=132 style='height:99.0pt'>  <td rowspan=4 height=307 class=xl68 width=163 style='border-bottom:1.0pt;  height:230.0pt;border-top:none;width:122pt'>deleteTopic</td>  <td rowspan=4 class=xl70 width=135 style='border-bottom:1.0pt;  border-top:none;width:101pt'>删除Topic</td>  <td class=xl65 width=149 style='width:112pt'>-c</td>  <td class=xl66 width=159 style='width:119pt'>cluster 名称，表示删除某集群下的某个 topic （集群  可通过 clusterList 查询）</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl65 width=149 style='height:17.0pt;width:112pt'>-h</td>  <td class=xl66 width=159 style='width:119pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=149 style='height:43.0pt;width:112pt'>-n</td>  <td class=xl66 width=159 style='width:119pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=95 style='height:71.0pt'>  <td height=95 class=xl65 width=149 style='height:71.0pt;width:112pt'>-t</td>  <td class=xl66 width=159 style='width:119pt'>topic 名称（名称只能使用字符  ^[a-zA-Z0-9_-]+$ ）</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=3 height=287 class=xl68 width=163 style='border-bottom:1.0pt;  height:215.0pt;border-top:none;width:122pt'>topicList</td>  <td rowspan=3 class=xl70 width=135 style='border-bottom:1.0pt;  border-top:none;width:101pt'>查看 Topic 列表信息</td>  <td class=xl65 width=149 style='width:112pt'>-h</td>  <td class=xl66 width=159 style='width:119pt'>打印帮助</td> </tr> <tr height=207 style='height:155.0pt'>  <td height=207 class=xl65 width=149 style='height:155.0pt;width:112pt'>-c</td>  <td class=xl66 width=159 style='width:119pt'>不配置-c只返回topic列表，增加-c返回clusterName,  topic, consumerGroup信息，即topic的所属集群和订阅关系，没有参数</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=149 style='height:43.0pt;width:112pt'>-n</td>  <td class=xl66 width=159 style='width:119pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=3 height=103 class=xl68 width=163 style='border-bottom:1.0pt;  height:77.0pt;border-top:none;width:122pt'>topicRoute</td>  <td rowspan=3 class=xl70 width=135 style='border-bottom:1.0pt;  border-top:none;width:101pt'>查看 Topic 路由信息</td>  <td class=xl65 width=149 style='width:112pt'>-t</td>  <td class=xl66 width=159 style='width:119pt'>topic 名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl65 width=149 style='height:17.0pt;width:112pt'>-h</td>  <td class=xl66 width=159 style='width:119pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=149 style='height:43.0pt;width:112pt'>-n</td>  <td class=xl66 width=159 style='width:119pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=3 height=103 class=xl68 width=163 style='border-bottom:1.0pt;  height:77.0pt;border-top:none;width:122pt'>topicStatus</td>  <td rowspan=3 class=xl70 width=135 style='border-bottom:1.0pt;  border-top:none;width:101pt'>查看 Topic 消息队列offset</td>  <td class=xl65 width=149 style='width:112pt'>-t</td>  <td class=xl66 width=159 style='width:119pt'>topic 名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl65 width=149 style='height:17.0pt;width:112pt'>-h</td>  <td class=xl66 width=159 style='width:119pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=149 style='height:43.0pt;width:112pt'>-n</td>  <td class=xl66 width=159 style='width:119pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=3 height=103 class=xl68 width=163 style='border-bottom:1.0pt;  height:77.0pt;border-top:none;width:122pt'>topicClusterList</td>  <td rowspan=3 class=xl70 width=135 style='border-bottom:1.0pt;  border-top:none;width:101pt'>查看 Topic 所在集群列表</td>  <td class=xl65 width=149 style='width:112pt'>-t</td>  <td class=xl66 width=159 style='width:119pt'>topic 名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl65 width=149 style='height:17.0pt;width:112pt'>-h</td>  <td class=xl66 width=159 style='width:119pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=149 style='height:43.0pt;width:112pt'>-n</td>  <td class=xl66 width=159 style='width:119pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=6 height=518 class=xl68 width=163 style='border-bottom:1.0pt;  height:380pt;border-top:none;width:122pt'>updateTopicPerm</td>  <td rowspan=6 class=xl70 width=135 style='border-bottom:1.0pt;  border-top:none;width:101pt'>更新 Topic 读写权限</td>  <td class=xl65 width=149 style='width:112pt'>-t</td>  <td class=xl66 width=159 style='width:119pt'>topic 名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl65 width=149 style='height:17.0pt;width:112pt'>-h</td>  <td class=xl66 width=159 style='width:119pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=149 style='height:43.0pt;width:112pt'>-n</td>  <td class=xl66 width=159 style='width:119pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=132 style='height:99.0pt'>  <td height=132 class=xl65 width=149 style='height:99.0pt;width:112pt'>-b</td>  <td class=xl66 width=159 style='width:119pt'>Broker 地址，表示 topic 所在  Broker，只支持单台Broker，地址为ip:port</td> </tr> <tr height=76 style='height:57.0pt'>  <td height=76 class=xl65 width=149 style='height:57.0pt;width:112pt'>-p</td>  <td class=xl66 width=159 style='width:119pt'>指定新 topic 的读写权限( W=2|R=4|WR=6 )</td> </tr> <tr height=207 style='height:155.0pt'>  <td height=207 class=xl65 width=149 style='height:155.0pt;width:112pt'>-c</td>  <td class=xl66 width=159 style='width:119pt'>cluster 名称，表示 topic 所在集群（集群可通过  clusterList 查询），-b优先，如果没有-b，则对集群中所有Broker执行命令</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=5 height=199 class=xl68 width=163 style='border-bottom:1.0pt;  height:149.0pt;border-top:none;width:122pt'>updateOrderConf</td>  <td rowspan=5 class=xl70 width=135 style='border-bottom:1.0pt;  border-top:none;width:101pt'>从NameServer上创建、删除、获取特定命名空间的kv配置，目前还未启用</td>  <td class=xl65 width=149 style='width:112pt'>-h</td>  <td class=xl66 width=159 style='width:119pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=149 style='height:43.0pt;width:112pt'>-n</td>  <td class=xl66 width=159 style='width:119pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl65 width=149 style='height:17.0pt;width:112pt'>-t</td>  <td class=xl66 width=159 style='width:119pt'>topic，键</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl65 width=149 style='height:29.0pt;width:112pt'>-v</td>  <td class=xl66 width=159 style='width:119pt'>orderConf，值</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=149 style='height:43.0pt;width:112pt'>-m</td>  <td class=xl66 width=159 style='width:119pt'>method，可选get、put、delete</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=4 height=198 class=xl68 width=163 style='border-bottom:1.0pt;  height:140pt;border-top:none;width:122pt'>allocateMQ</td>  <td rowspan=4 class=xl70 width=135 style='border-bottom:1.0pt;  border-top:none;width:101pt'>以平均负载算法计算消费者列表负载消息队列的负载结果</td>  <td class=xl65 width=149 style='width:112pt'>-t</td>  <td class=xl66 width=159 style='width:119pt'>topic 名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl65 width=149 style='height:17.0pt;width:112pt'>-h</td>  <td class=xl66 width=159 style='width:119pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=149 style='height:43.0pt;width:112pt'>-n</td>  <td class=xl66 width=159 style='width:119pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=95 style='height:71.0pt'>  <td height=95 class=xl65 width=149 style='height:71.0pt;width:112pt'>-i</td>  <td class=xl66 width=159 style='width:119pt'>ipList，用逗号分隔，计算这些ip去负载Topic的消息队列</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=4 height=142 class=xl68 width=163 style='border-bottom:1.0pt solid black;  height:106.0pt;border-top:1.0pt;width:122pt'>statsAll</td>  <td rowspan=4 class=xl70 width=135 style='border-bottom:1.0pt;  border-top:none;width:101pt'>打印Topic订阅关系、TPS、积累量、24h读写总量等信息</td>  <td class=xl65 width=149 style='width:112pt'>-h</td>  <td class=xl66 width=159 style='width:119pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=149 style='height:43.0pt;width:112pt'>-n</td>  <td class=xl66 width=159 style='width:119pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl65 width=149 style='height:29.0pt;width:112pt'>-a</td>  <td class=xl66 width=159 style='width:119pt'>是否只打印活跃topic</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl65 width=149 style='height:17.0pt;width:112pt'>-t</td>  <td class=xl66 width=159 style='width:119pt'>指定topic</td> </tr></table><h4 id="2-2-集群相关"><a href="#2-2-集群相关" class="headerlink" title="2.2 集群相关"></a>2.2 集群相关</h4><table border=0 cellpadding=0 cellspacing=0 width=714> <col width=177> <col width=175> <col width=177> <col width=185> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl63 width=177 style='height:17.0pt;width:133pt'>名称</td>  <td class=xl64 width=175 style='width:131pt'>含义</td>  <td class=xl64 width=177 style='width:133pt'>命令选项</td>  <td class=xl64 width=185 style='width:139pt'>说明</td> </tr> <tr height=207 style='height:155.0pt'>  <td rowspan=4 height=326 class=xl67 width=177 style='border-bottom:1.0pt;  height:244.0pt;border-top:none;width:133pt'><span  style='mso-spacerun:yes'> </span>clusterList</td>  <td rowspan=4 class=xl70 width=175 style='border-bottom:1.0pt;  border-top:none;width:131pt'>查看集群信息，集群、BrokerName、BrokerId、TPS等信息</td>  <td class=xl65 width=177 style='width:133pt'>-m</td>  <td class=xl66 width=185 style='width:139pt'>打印更多信息 (增加打印出如下信息 #InTotalYest,  #OutTotalYest, #InTotalToday ,#OutTotalToday)</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl65 width=177 style='height:17.0pt;width:133pt'>-h</td>  <td class=xl66 width=185 style='width:139pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=177 style='height:43.0pt;width:133pt'>-n</td>  <td class=xl66 width=185 style='width:139pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl65 width=177 style='height:29.0pt;width:133pt'>-i</td>  <td class=xl66 width=185 style='width:139pt'>打印间隔，单位秒</td> </tr> <tr height=95 style='height:71.0pt'>  <td rowspan=8 height=391 class=xl67 width=177 style='border-bottom:1.0pt;  height:292.0pt;border-top:none;width:133pt'>clusterRT</td>  <td rowspan=8 class=xl70 width=175 style='border-bottom:1.0pt;  border-top:none;width:131pt'>发送消息检测集群各Broker RT。消息发往${BrokerName} Topic。</td>  <td class=xl65 width=177 style='width:133pt'>-a</td>  <td class=xl66 width=185 style='width:139pt'>amount，每次探测的总数，RT = 总时间 /  amount</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl65 width=177 style='height:29.0pt;width:133pt'>-s</td>  <td class=xl66 width=185 style='width:139pt'>消息大小，单位B</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl65 width=177 style='height:17.0pt;width:133pt'>-c</td>  <td class=xl66 width=185 style='width:139pt'>探测哪个集群</td> </tr> <tr height=76 style='height:57.0pt'>  <td height=76 class=xl65 width=177 style='height:57.0pt;width:133pt'>-p</td>  <td class=xl66 width=185 style='width:139pt'>是否打印格式化日志，以|分割，默认不打印</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl65 width=177 style='height:17.0pt;width:133pt'>-h</td>  <td class=xl66 width=185 style='width:139pt'>打印帮助</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl65 width=177 style='height:29.0pt;width:133pt'>-m</td>  <td class=xl66 width=185 style='width:139pt'>所属机房，打印使用</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl65 width=177 style='height:29.0pt;width:133pt'>-i</td>  <td class=xl66 width=185 style='width:139pt'>发送间隔，单位秒</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl65 width=177 style='height:43.0pt;width:133pt'>-n</td>  <td class=xl66 width=185 style='width:139pt'>NameServer 服务地址，格式 ip:port</td> </tr></table><h4 id="2-3-Broker相关"><a href="#2-3-Broker相关" class="headerlink" title="2.3 Broker相关"></a>2.3 Broker相关</h4><table border=0 cellpadding=0 cellspacing=0 width=714> <col width=177> <col width=175> <col width=177> <col width=185> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl63 width=177 style='height:17.0pt;width:133pt'>名称</td>  <td class=xl64 width=175 style='width:131pt'>含义</td>  <td class=xl64 width=177 style='width:133pt'>命令选项</td>  <td class=xl64 width=185 style='width:139pt'>说明</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=6 height=206 class=xl69 width=191 style='border-bottom:1.0pt;  height:154.0pt;border-top:none;width:143pt'>updateBrokerConfig</td>  <td rowspan=6 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>更新 Broker 配置文件，会修改Broker.conf</td>  <td class=xl67 width=87 style='width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>Broker 地址，格式为ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-c</td>  <td class=xl68 width=87 style='width:65pt'>cluster 名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-k</td>  <td class=xl68 width=87 style='width:65pt'>key 值</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-v</td>  <td class=xl68 width=87 style='width:65pt'>value 值</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=3 height=137 class=xl69 width=191 style='border-bottom:1.0pt;  height:103.0pt;border-top:none;width:143pt'>brokerStatus</td>  <td rowspan=3 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>查看 Broker 统计信息、运行状态（你想要的信息几乎都在里面）</td>  <td class=xl67 width=87 style='width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>Broker 地址，地址为ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=6 height=256 class=xl69 width=191 style='border-bottom:1.0pt;  height:192.0pt;border-top:none;width:143pt'>brokerConsumeStats</td>  <td rowspan=6 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>Broker中各个消费者的消费情况，按Message Queue维度返回Consume  Offset，Broker Offset，Diff，TImestamp等信息</td>  <td class=xl67 width=87 style='width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>Broker 地址，地址为ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>请求超时时间</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-l</td>  <td class=xl68 width=87 style='width:65pt'>diff阈值，超过阈值才打印</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-o</td>  <td class=xl68 width=87 style='width:65pt'>是否为顺序topic，一般为false</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=2 height=114 class=xl69 width=191 style='border-bottom:1.0pt;  height:86.0pt;border-top:none;width:143pt'>getBrokerConfig</td>  <td rowspan=2 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>获取Broker配置</td>  <td class=xl67 width=87 style='width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>Broker 地址，地址为ip:port</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=3 height=137 class=xl69 width=191 style='border-bottom:1.0pt;  height:103.0pt;border-top:none;width:143pt'>wipeWritePerm</td>  <td rowspan=3 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>从NameServer上清除 Broker写权限</td>  <td class=xl67 width=87 style='width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>Broker 地址，地址为ip:port</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=4 height=160 class=xl69 width=191 style='border-bottom:1.0pt;  height:120.0pt;border-top:none;width:143pt'>cleanExpiredCQ</td>  <td rowspan=4 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>清理Broker上过期的Consume Queue，如果手动减少对列数可能产生过期队列</td>  <td class=xl67 width=87 style='width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>Broker 地址，地址为ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-c</td>  <td class=xl68 width=87 style='width:65pt'>集群名称</td> </tr> <tr height=88 style='mso-height-source:userset;height:66.0pt'>  <td rowspan=4 height=191 class=xl69 width=191 style='border-bottom:1.0pt;  height:143.0pt;border-top:none;width:143pt'>cleanUnusedTopic</td>  <td rowspan=4 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>清理Broker上不使用的Topic，从内存中释放Topic的Consume  Queue，如果手动删除Topic会产生不使用的Topic</td>  <td class=xl67 width=87 style='width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>Broker 地址，地址为ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-c</td>  <td class=xl68 width=87 style='width:65pt'>集群名称</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=5 height=199 class=xl69 width=191 style='border-bottom:1.0pt;  height:149.0pt;border-top:none;width:143pt'>sendMsgStatus</td>  <td rowspan=5 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>向Broker发消息，返回发送状态和RT</td>  <td class=xl67 width=87 style='width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>BrokerName，注意不同于Broker地址</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-s</td>  <td class=xl68 width=87 style='width:65pt'>消息大小，单位B</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-c</td>  <td class=xl68 width=87 style='width:65pt'>发送次数</td> </tr></table><h4 id="2-4-消息相关"><a href="#2-4-消息相关" class="headerlink" title="2.4 消息相关"></a>2.4 消息相关</h4><table border=0 cellpadding=0 cellspacing=0 width=714> <col width=177> <col width=175> <col width=177> <col width=185><tr height=23 style='height:17.0pt'>  <td height=23 class=xl63 width=177 style='height:17.0pt;width:133pt'>名称</td>  <td class=xl64 width=175 style='width:131pt'>含义</td>  <td class=xl64 width=177 style='width:133pt'>命令选项</td>  <td class=xl64 width=185 style='width:139pt'>说明</td> </tr> <tr height=128 style='height:96.0pt'>  <td rowspan=3 height=208 class=xl69 width=87 style='border-bottom:1.0pt;  height:156.0pt;border-top:none;width:65pt'>queryMsgById</td>  <td rowspan=3 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>根据offsetMsgId查询msg，如果使用开源控制台，应使用offsetMsgId，此命令还有其他参数，具体作用请阅读QueryMsgByIdSubCommand。</td>  <td class=xl67 width=87 style='width:65pt'>-i</td>  <td class=xl67 width=87 style='width:65pt'>msgId</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=4 height=126 class=xl69 width=87 style='border-bottom:1.0pt;  height:94.0pt;border-top:none;width:65pt'>queryMsgByKey</td>  <td rowspan=4 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>根据消息 Key 查询消息</td>  <td class=xl67 width=87 style='width:65pt'>-k</td>  <td class=xl67 width=87 style='width:65pt'>msgKey</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>Topic 名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=225 style='height:169.0pt'>  <td rowspan=6 height=390 class=xl69 width=87 style='border-bottom:1.0pt;  height:292.0pt;border-top:none;width:65pt'>queryMsgByOffset</td>  <td rowspan=6 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>根据 Offset 查询消息</td>  <td class=xl67 width=87 style='width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>Broker 名称，（这里需要注意  填写的是 Broker 的名称，不是 Broker 的地址，Broker 名称可以在 clusterList 查到）</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-i</td>  <td class=xl68 width=87 style='width:65pt'>query 队列 id</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-o</td>  <td class=xl68 width=87 style='width:65pt'>offset 值</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>topic 名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=47>  <td rowspan=6 height=209 class=xl69 width=87 style='border-bottom:1.0pt;  height:156.0pt;border-top:none;width:65pt'>queryMsgByUniqueKey</td>  <td rowspan=6 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>根据msgId查询，msgId不同于offsetMsgId，区别详见常见运维问题。-g，-d配合使用，查到消息后尝试让特定的消费者消费消息并返回消费结果</td>  <td class=xl67 width=87 style='width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-i</td>  <td class=xl67 width=87 style='width:65pt'>uniqe msg id</td> </tr> <tr height=36 style='height:27.0pt'>  <td height=36 class=xl67 width=87 style='height:27.0pt;width:65pt'>-g</td>  <td class=xl67 width=87 style='width:65pt'>consumerGroup</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-d</td>  <td class=xl67 width=87 style='width:65pt'>clientId</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>topic名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=5 height=149 class=xl69 width=87 style='border-bottom:1.0pt  height:111.0pt;border-top:none;width:65pt'>checkMsgSendRT</td>  <td rowspan=5 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>检测向topic发消息的RT，功能类似clusterRT</td>  <td class=xl67 width=87 style='width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>topic名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-a</td>  <td class=xl68 width=87 style='width:65pt'>探测次数</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-s</td>  <td class=xl68 width=87 style='width:65pt'>消息大小</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=8 height=218 class=xl69 width=87 style='border-bottom:1.0pt;  height:162.0pt;border-top:none;width:65pt'>sendMessage</td>  <td rowspan=8 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>发送一条消息，可以根据配置发往特定Message Queue，或普通发送。</td>  <td class=xl67 width=87 style='width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>topic名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-p</td>  <td class=xl68 width=87 style='width:65pt'>body，消息体</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-k</td>  <td class=xl67 width=87 style='width:65pt'>keys</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-c</td>  <td class=xl67 width=87 style='width:65pt'>tags</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-b</td>  <td class=xl67 width=87 style='width:65pt'>BrokerName</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-i</td>  <td class=xl67 width=87 style='width:65pt'>queueId</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=10 height=312 class=xl69 width=87 style='border-bottom:1.0pt;  height:232.0pt;border-top:none;width:65pt'>consumeMessage</td>  <td rowspan=10 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>消费消息。可以根据offset、开始&amp;结束时间戳、消息队列消费消息，配置不同执行不同消费逻辑，详见ConsumeMessageCommand。</td>  <td class=xl67 width=87 style='width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>topic名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-b</td>  <td class=xl67 width=87 style='width:65pt'>BrokerName</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-o</td>  <td class=xl68 width=87 style='width:65pt'>从offset开始消费</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-i</td>  <td class=xl67 width=87 style='width:65pt'>queueId</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-g</td>  <td class=xl68 width=87 style='width:65pt'>消费者分组</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-s</td>  <td class=xl68 width=87 style='width:65pt'>开始时间戳，格式详见-h</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-d</td>  <td class=xl68 width=87 style='width:65pt'>结束时间戳</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-c</td>  <td class=xl68 width=87 style='width:65pt'>消费多少条消息</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=8 height=282 class=xl69 width=87 style='border-bottom:1.0pt;  height:210.0pt;border-top:none;width:65pt'>printMsg</td>  <td rowspan=8 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>从Broker消费消息并打印，可选时间段</td>  <td class=xl67 width=87 style='width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>topic名称</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-c</td>  <td class=xl68 width=87 style='width:65pt'>字符集，例如UTF-8</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-s</td>  <td class=xl68 width=87 style='width:65pt'>subExpress，过滤表达式</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>开始时间戳，格式参见-h</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-e</td>  <td class=xl68 width=87 style='width:65pt'>结束时间戳</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-d</td>  <td class=xl68 width=87 style='width:65pt'>是否打印消息体</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=12 height=390 class=xl69 width=87 style='border-bottom:1.0pt;  height:290.0pt;border-top:none;width:65pt'>printMsgByQueue</td>  <td rowspan=12 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>类似printMsg，但指定Message Queue</td>  <td class=xl67 width=87 style='width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>topic名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-i</td>  <td class=xl67 width=87 style='width:65pt'>queueId</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-a</td>  <td class=xl67 width=87 style='width:65pt'>BrokerName</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-c</td>  <td class=xl68 width=87 style='width:65pt'>字符集，例如UTF-8</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-s</td>  <td class=xl68 width=87 style='width:65pt'>subExpress，过滤表达式</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>开始时间戳，格式参见-h</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-e</td>  <td class=xl68 width=87 style='width:65pt'>结束时间戳</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-p</td>  <td class=xl68 width=87 style='width:65pt'>是否打印消息</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-d</td>  <td class=xl68 width=87 style='width:65pt'>是否打印消息体</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-f</td>  <td class=xl68 width=87 style='width:65pt'>是否统计tag数量并打印</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=7 height=410 class=xl69 width=87 style='border-bottom:1.0pt;  height:307.0pt;border-top:none;width:65pt'>resetOffsetByTime</td>  <td rowspan=7 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>按时间戳重置offset，Broker和consumer都会重置</td>  <td class=xl67 width=87 style='width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-g</td>  <td class=xl68 width=87 style='width:65pt'>消费者分组</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>topic名称</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-s</td>  <td class=xl68 width=87 style='width:65pt'>重置为此时间戳对应的offset</td> </tr> <tr height=188 style='height:141.0pt'>  <td height=188 class=xl67 width=87 style='height:141.0pt;width:65pt'>-f</td>  <td class=xl68 width=87 style='width:65pt'>是否强制重置，如果false，只支持回溯offset，如果true，不管时间戳对应offset与consumeOffset关系</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-c</td>  <td class=xl68 width=87 style='width:65pt'>是否重置c++客户端offset</td> </tr></table><h4 id="2-5-消费者、消费组相关"><a href="#2-5-消费者、消费组相关" class="headerlink" title="2.5 消费者、消费组相关"></a>2.5 消费者、消费组相关</h4><table border=0 cellpadding=0 cellspacing=0 width=714> <col width=177> <col width=175> <col width=177> <col width=185><tr height=23 style='height:17.0pt'>  <td height=23 class=xl63 width=177 style='height:17.0pt;width:133pt'>名称</td>  <td class=xl64 width=175 style='width:131pt'>含义</td>  <td class=xl64 width=177 style='width:133pt'>命令选项</td>  <td class=xl64 width=185 style='width:139pt'>说明</td> </tr> <tr height=39 style='height:29.0pt'>  <td rowspan=4 height=158 class=xl69 width=87 style='border-bottom:1.0pt;  height:110pt;border-top:none;width:65pt'>consumerProgress</td>  <td rowspan=4 class=xl72 width=87 style='border-bottom:1.0pt;  border-top:none;width:65pt'>查看订阅组消费状态，可以查看具体的client IP的消息积累量</td>  <td class=xl67 width=87 style='width:65pt'>-g</td>  <td class=xl68 width=87 style='width:65pt'>消费者所属组名</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-s</td>  <td class=xl68 width=87 style='width:65pt'>是否打印client IP</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=105 style='mso-height-source:userset;height:79.0pt'>  <td rowspan=5 height=260 class=xl69 width=87 style='border-bottom:1.0pt;  height:195.0pt;border-top:none;width:65pt'>consumerStatus</td>  <td rowspan=5 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>查看消费者状态，包括同一个分组中是否都是相同的订阅，分析Process  Queue是否堆积，返回消费者jstack结果，内容较多，使用者参见ConsumerStatusSubCommand</td>  <td class=xl67 width=87 style='width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=36 style='height:27.0pt'>  <td height=36 class=xl67 width=87 style='height:27.0pt;width:65pt'>-g</td>  <td class=xl67 width=87 style='width:65pt'>consumer group</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-i</td>  <td class=xl67 width=87 style='width:65pt'>clientId</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-s</td>  <td class=xl68 width=87 style='width:65pt'>是否执行jstack</td> </tr> <tr height=39 style='height:29.0pt'>  <td rowspan=5 height=181 class=xl69 width=87 style='border-bottom:1.0pt  height:135.0pt;border-top:none;width:65pt'>getConsumerStatus</td>  <td rowspan=5 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>获取 Consumer 消费进度</td>  <td class=xl67 width=87 style='width:65pt'>-g</td>  <td class=xl68 width=87 style='width:65pt'>消费者所属组名</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>查询主题</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-i</td>  <td class=xl68 width=87 style='width:65pt'>Consumer 客户端 ip</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=13 height=761 class=xl69 width=87 style='border-bottom:1.0pt  height:569.0pt;border-top:none;width:65pt'>updateSubGroup</td>  <td rowspan=13 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>更新或创建订阅关系</td>  <td class=xl67 width=87 style='width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>Broker地址</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-c</td>  <td class=xl68 width=87 style='width:65pt'>集群名称</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-g</td>  <td class=xl68 width=87 style='width:65pt'>消费者分组名称</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-s</td>  <td class=xl68 width=87 style='width:65pt'>分组是否允许消费</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-m</td>  <td class=xl68 width=87 style='width:65pt'>是否从最小offset开始消费</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-d</td>  <td class=xl68 width=87 style='width:65pt'>是否是广播模式</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-q</td>  <td class=xl68 width=87 style='width:65pt'>重试队列数量</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-r</td>  <td class=xl68 width=87 style='width:65pt'>最大重试次数</td> </tr> <tr height=207 style='height:155.0pt'>  <td height=207 class=xl67 width=87 style='height:155.0pt;width:65pt'>-i</td>  <td class=xl68 width=87 style='width:65pt'>当slaveReadEnable开启时有效，且还未达到从slave消费时建议从哪个BrokerId消费，可以配置备机id，主动从备机消费</td> </tr> <tr height=132 style='height:99.0pt'>  <td height=132 class=xl67 width=87 style='height:99.0pt;width:65pt'>-w</td>  <td class=xl68 width=87 style='width:65pt'>如果Broker建议从slave消费，配置决定从哪个slave消费，配置BrokerId，例如1</td> </tr> <tr height=76 style='height:57.0pt'>  <td height=76 class=xl67 width=87 style='height:57.0pt;width:65pt'>-a</td>  <td class=xl68 width=87 style='width:65pt'>当消费者数量变化时是否通知其他消费者负载均衡</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=5 height=165 class=xl69 width=87 style='border-bottom:1.0pt  height:123.0pt;border-top:none;width:65pt'>deleteSubGroup</td>  <td rowspan=5 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>从Broker删除订阅关系</td>  <td class=xl67 width=87 style='width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-b</td>  <td class=xl68 width=87 style='width:65pt'>Broker地址</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-c</td>  <td class=xl68 width=87 style='width:65pt'>集群名称</td> </tr> <tr height=39 style='height:29.0pt'>  <td height=39 class=xl67 width=87 style='height:29.0pt;width:65pt'>-g</td>  <td class=xl68 width=87 style='width:65pt'>消费者分组名称</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=6 height=172 class=xl69 width=87 style='border-bottom:1.0pt  height:120pt;border-top:none;width:65pt'>cloneGroupOffset</td>  <td rowspan=6 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>在目标群组中使用源群组的offset</td>  <td class=xl67 width=87 style='width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-s</td>  <td class=xl68 width=87 style='width:65pt'>源消费者组</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-d</td>  <td class=xl68 width=87 style='width:65pt'>目标消费者组</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>topic名称</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-o</td>  <td class=xl68 width=87 style='width:65pt'>暂未使用</td> </tr></table><h4 id="2-6-连接相关"><a href="#2-6-连接相关" class="headerlink" title="2.6 连接相关"></a>2.6 连接相关</h4><table border=0 cellpadding=0 cellspacing=0 width=714> <col width=177> <col width=175> <col width=177> <col width=185><tr height=23 style='height:17.0pt'>  <td height=23 class=xl63 width=177 style='height:17.0pt;width:133pt'>名称</td>  <td class=xl64 width=175 style='width:131pt'>含义</td>  <td class=xl64 width=177 style='width:133pt'>命令选项</td>  <td class=xl64 width=185 style='width:139pt'>说明</td> </tr> <tr height=39 style='height:29.0pt'>  <td rowspan=3 height=119 class=xl69 width=87 style='border-bottom:1.0pt  height:89.0pt;border-top:none;width:65pt'>consumerConnec tion</td>  <td rowspan=3 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>查询 Consumer 的网络连接</td>  <td class=xl67 width=87 style='width:65pt'>-g</td>  <td class=xl68 width=87 style='width:65pt'>消费者所属组名</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=39 style='height:29.0pt'>  <td rowspan=4 height=142 class=xl69 width=87 style='border-bottom:1.0pt  height:106.0pt;border-top:none;width:65pt'>producerConnec tion</td>  <td rowspan=4 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>查询 Producer 的网络连接</td>  <td class=xl67 width=87 style='width:65pt'>-g</td>  <td class=xl68 width=87 style='width:65pt'>生产者所属组名</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-t</td>  <td class=xl68 width=87 style='width:65pt'>主题名称</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr></table><h4 id="2-7-NameServer相关"><a href="#2-7-NameServer相关" class="headerlink" title="2.7 NameServer相关"></a>2.7 NameServer相关</h4><table border=0 cellpadding=0 cellspacing=0 width=714> <col width=177> <col width=175> <col width=177> <col width=185><tr height=23 style='height:17.0pt'>  <td height=23 class=xl63 width=177 style='height:17.0pt;width:133pt'>名称</td>  <td class=xl64 width=175 style='width:131pt'>含义</td>  <td class=xl64 width=177 style='width:133pt'>命令选项</td>  <td class=xl64 width=185 style='width:139pt'>说明</td> </tr> <tr height=21 style='height:16.0pt'>  <td rowspan=5 height=143 class=xl69 width=87 style='border-bottom:1.0pt  height:100pt;border-top:none;width:65pt'>updateKvConfig</td>  <td rowspan=5 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>更新NameServer的kv配置，目前还未使用</td>  <td class=xl75 width=87 style='width:65pt'>-s</td>  <td class=xl76 width=87 style='width:65pt'>命名空间</td> </tr> <tr height=21 style='height:16.0pt'>  <td height=21 class=xl75 width=87 style='height:16.0pt;width:65pt'>-k</td>  <td class=xl75 width=87 style='width:65pt'>key</td> </tr> <tr height=21 style='height:16.0pt'>  <td height=21 class=xl75 width=87 style='height:16.0pt;width:65pt'>-v</td>  <td class=xl75 width=87 style='width:65pt'>value</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=23 style='height:17.0pt'>  <td rowspan=4 height=126 class=xl69 width=87 style='border-bottom:1.0pt  height:94.0pt;border-top:none;width:65pt'>deleteKvConfig</td>  <td rowspan=4 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>删除NameServer的kv配置</td>  <td class=xl67 width=87 style='width:65pt'>-s</td>  <td class=xl68 width=87 style='width:65pt'>命名空间</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-k</td>  <td class=xl67 width=87 style='width:65pt'>key</td> </tr> <tr height=57 style='height:43.0pt'>  <td height=57 class=xl67 width=87 style='height:43.0pt;width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=2 height=80 class=xl69 width=87 style='border-bottom:1.0pt  height:60.0pt;border-top:none;width:65pt'>getNamesrvConfig</td>  <td rowspan=2 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>获取NameServer配置</td>  <td class=xl67 width=87 style='width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=4 height=126 class=xl69 width=87 style='border-bottom:1.0pt  height:94.0pt;border-top:none;width:65pt'>updateNamesrvConfig</td>  <td rowspan=4 class=xl72 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>修改NameServer配置</td>  <td class=xl67 width=87 style='width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-k</td>  <td class=xl67 width=87 style='width:65pt'>key</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-v</td>  <td class=xl67 width=87 style='width:65pt'>value</td> </tr></table><h4 id="2-8-其他"><a href="#2-8-其他" class="headerlink" title="2.8 其他"></a>2.8 其他</h4><table border=0 cellpadding=0 cellspacing=0 width=714> <col width=177> <col width=175> <col width=177> <col width=185><tr height=23 style='height:17.0pt'>  <td height=23 class=xl63 width=177 style='height:17.0pt;width:133pt'>名称</td>  <td class=xl64 width=175 style='width:131pt'>含义</td>  <td class=xl64 width=177 style='width:133pt'>命令选项</td>  <td class=xl64 width=185 style='width:139pt'>说明</td> </tr> <tr height=57 style='height:43.0pt'>  <td rowspan=2 height=80 class=xl69 width=87 style='border-bottom:1.0pt  height:60.0pt;border-top:none;width:65pt'>startMonitoring</td>  <td rowspan=2 class=xl71 width=87 style='border-bottom:1.0pt  border-top:none;width:65pt'>开启监控进程，监控消息误删、重试队列消息数等</td>  <td class=xl67 width=87 style='width:65pt'>-n</td>  <td class=xl68 width=87 style='width:65pt'>NameServer 服务地址，格式 ip:port</td> </tr> <tr height=23 style='height:17.0pt'>  <td height=23 class=xl67 width=87 style='height:17.0pt;width:65pt'>-h</td>  <td class=xl68 width=87 style='width:65pt'>打印帮助</td> </tr></table><h3 id="3-运维常见问题"><a href="#3-运维常见问题" class="headerlink" title="3   运维常见问题"></a>3   运维常见问题</h3><h4 id="3-1-RocketMQ的mqadmin命令报错问题"><a href="#3-1-RocketMQ的mqadmin命令报错问题" class="headerlink" title="3.1 RocketMQ的mqadmin命令报错问题"></a>3.1 RocketMQ的mqadmin命令报错问题</h4><blockquote><p> 问题描述：有时候在部署完RocketMQ集群后，尝试执行“mqadmin”一些运维命令，会出现下面的异常信息：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to &lt;<span class="literal">null</span>&gt; failed</span><br></pre></td></tr></table></figure></blockquote><p>解决方法：可以在部署RocketMQ集群的虚拟机上执行<code>export NAMESRV_ADDR=ip:9876</code>（ip指的是集群中部署NameServer组件的机器ip地址）命令之后再使用“mqadmin”的相关命令进行查询，即可得到结果。</p><h4 id="3-2-RocketMQ生产端和消费端版本不一致导致不能正常消费的问题"><a href="#3-2-RocketMQ生产端和消费端版本不一致导致不能正常消费的问题" class="headerlink" title="3.2 RocketMQ生产端和消费端版本不一致导致不能正常消费的问题"></a>3.2 RocketMQ生产端和消费端版本不一致导致不能正常消费的问题</h4><blockquote><p>问题描述：同一个生产端发出消息，A消费端可消费，B消费端却无法消费，rocketMQ Console中出现：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Not found the consumer group consume stats, because <span class="keyword">return</span> offset table is empty, maybe the consumer not consume any message的异常消息。</span><br></pre></td></tr></table></figure></blockquote><p>  解决方案：RocketMQ 的jar包：rocketmq-client等包应该保持生产端，消费端使用相同的version。</p><h4 id="3-3-新增一个topic的消费组时，无法消费历史消息的问题"><a href="#3-3-新增一个topic的消费组时，无法消费历史消息的问题" class="headerlink" title="3.3  新增一个topic的消费组时，无法消费历史消息的问题"></a>3.3  新增一个topic的消费组时，无法消费历史消息的问题</h4><blockquote><p>问题描述：当同一个topic的新增消费组启动时，消费的消息是当前的offset的消息，并未获取历史消息。    </p></blockquote><p>解决方案：rocketmq默认策略是从消息队列尾部，即跳过历史消息。如果想消费历史消息，则需要设置：<code>org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#setConsumeFromWhere</code>。常用的有以下三种配置：</p><ul><li>默认配置,一个新的订阅组第一次启动从队列的最后位置开始消费，后续再启动接着上次消费的进度开始消费,即跳过历史消息；</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);</span><br></pre></td></tr></table></figure><ul><li>一个新的订阅组第一次启动从队列的最前位置开始消费，后续再启动接着上次消费的进度开始消费,即消费Broker未过期的历史消息；</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);</span><br></pre></td></tr></table></figure><ul><li>一个新的订阅组第一次启动从指定时间点开始消费，后续再启动接着上次消费的进度开始消费，和consumer.setConsumeTimestamp()配合使用，默认是半个小时以前；</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_TIMESTAMP);</span><br></pre></td></tr></table></figure><h4 id="3-4-如何开启从Slave读数据功能"><a href="#3-4-如何开启从Slave读数据功能" class="headerlink" title="3.4 如何开启从Slave读数据功能"></a>3.4 如何开启从Slave读数据功能</h4><p>在某些情况下，Consumer需要将消费位点重置到1-2天前，这时在内存有限的Master Broker上，CommitLog会承载比较重的IO压力，影响到该Broker的其它消息的读与写。可以开启<code>slaveReadEnable=true</code>，当Master Broker发现Consumer的消费位点与CommitLog的最新值的差值的容量超过该机器内存的百分比（<code>accessMessageInMemoryMaxRatio=40%</code>），会推荐Consumer从Slave Broker中去读取数据，降低Master Broker的IO。</p><h4 id="3-5-性能调优问题"><a href="#3-5-性能调优问题" class="headerlink" title="3.5 性能调优问题"></a>3.5 性能调优问题</h4><p>异步刷盘建议使用自旋锁，同步刷盘建议使用重入锁，调整Broker配置项<code>useReentrantLockWhenPutMessage</code>，默认为false；异步刷盘建议开启<code>TransientStorePoolEnable</code>；建议关闭transferMsgByHeap，提高拉消息效率；同步刷盘建议适当增大<code>sendMessageThreadPoolNums</code>，具体配置需要经过压测。</p><h4 id="3-6-在RocketMQ中msgId和offsetMsgId的含义与区别"><a href="#3-6-在RocketMQ中msgId和offsetMsgId的含义与区别" class="headerlink" title="3.6 在RocketMQ中msgId和offsetMsgId的含义与区别"></a>3.6 在RocketMQ中msgId和offsetMsgId的含义与区别</h4><p>使用RocketMQ完成生产者客户端消息发送后，通常会看到如下日志打印信息：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SendResult [sendStatus=SEND_OK, msgId=0A42333A0DC818B4AAC246C290FD0000, offsetMsgId=0A42333A00002A9F000000000134F1F5, messageQueue=MessageQueue [topic=topicTest1, BrokerName=mac.local, queueId=<span class="number">3</span>], queueOffset=<span class="number">4</span>]</span><br></pre></td></tr></table></figure><ul><li>msgId，对于客户端来说msgId是由客户端producer实例端生成的，具体来说，调用方法<code>MessageClientIDSetter.createUniqIDBuffer()</code>生成唯一的Id；</li><li>offsetMsgId，offsetMsgId是由Broker服务端在写入消息时生成的（采用”IP地址+Port端口”与“CommitLog的物理偏移量地址”做了一个字符串拼接），其中offsetMsgId就是在RocketMQ控制台直接输入查询的那个messageId。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;运维管理&quot;&gt;&lt;a href=&quot;#运维管理&quot; class=&quot;headerlink&quot; title=&quot;运维管理&quot;&gt;&lt;/a&gt;运维管理&lt;/h1&gt;&lt;hr&gt;
&lt;h3 id=&quot;1-集群搭建&quot;&gt;&lt;a href=&quot;#1-集群搭建&quot; class=&quot;headerlink&quot; title=&quot;</summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="运维管理" scheme="https://nydia.github.io/tags/%E8%BF%90%E7%BB%B4%E7%AE%A1%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ System Configuration</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/en/Configuration_System/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/en/Configuration_System/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T12:59:56.959Z</updated>
    
    <content type="html"><![CDATA[<h1 id="The-system-configuration"><a href="#The-system-configuration" class="headerlink" title="The system configuration"></a>The system configuration</h1><p>This section focuses on the configuration of the system (JVM&#x2F;OS)</p><h2 id="1-JVM-Options"><a href="#1-JVM-Options" class="headerlink" title="1 JVM Options"></a><strong>1 JVM Options</strong></h2><p>The latest released version of JDK 1.8 is recommended. Set the same Xms and Xmx value to prevent the JVM from resizing the heap for better performance. A simple JVM configuration is as follows:</p><pre><code>-server -Xms8g -Xmx8g -Xmn4g</code></pre><p>Direct ByteBuffer memory size setting. Full GC will be triggered when the Direct ByteBuffer up to the specified size:</p><pre><code>-XX:MaxDirectMemorySize=15g</code></pre><p>If you don’t care about the boot time of RocketMQ broker, pre-touch the Java heap to make sure that every page will be allocated during JVM initialization is a better choice. Those who don’t care about the boot time can enable it:</p><pre><code>-XX:+AlwaysPreTouch</code></pre><p>Disable biased locking maybe reduce JVM pauses:</p><pre><code>-XX:-UseBiasedLocking</code></pre><p>As for garbage collection, G1 collector with JDK 1.8 is recommended:</p><pre><code>-XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25-XX:InitiatingHeapOccupancyPercent=30</code></pre><p>These GC options looks a little aggressive, but it’s proved to have good performance in our production environment</p><p>Don’t set a too small value for -XX:MaxGCPauseMillis, otherwise JVM will use a small young generation to achieve this goal which will cause very frequent minor GC.So use rolling GC log file is recommended:</p><pre><code>-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m</code></pre><p>If write GC file will increase latency of broker, consider redirect GC log file to a memory file system:</p><pre><code>-Xloggc:/dev/shm/mq_gc_%p.log123</code></pre><h2 id="2-Linux-Kernel-Parameters"><a href="#2-Linux-Kernel-Parameters" class="headerlink" title="2 Linux Kernel Parameters"></a>2 Linux Kernel Parameters</h2><p>There is a os.sh script that lists a lot of kernel parameters in folder bin which can be used for production use with minor changes. Below parameters need attention, and more details please refer to documentation for &#x2F;proc&#x2F;sys&#x2F;vm&#x2F;*.</p><ul><li><p><strong>vm.extra_free_kbytes</strong>, tells the VM to keep extra free memory between the threshold where background reclaim (kswapd) kicks in, and the threshold where direct reclaim (by allocating processes) kicks in. RocketMQ uses this parameter to avoid high latency in memory allocation. (It is specific to the kernel version）</p></li><li><p><strong>vm.min_free_kbytes</strong>, if you set this to lower than 1024KB, your system will become subtly broken, and prone to deadlock under high loads.</p></li><li><p><strong>vm.max_map_count</strong>, limits the maximum number of memory map areas a process may have. RocketMQ will use mmap to load CommitLog and ConsumeQueue, so set a bigger value for this parameter is recommended.</p></li><li><p><strong>vm.swappiness</strong>, define how aggressive the kernel will swap memory pages. Higher values will increase agressiveness, lower values decrease the amount of swap. 10 is recommended for this value to avoid swap latency.</p></li><li><p><strong>File descriptor limits</strong>, RocketMQ needs open file descriptors for files(CommitLog and ConsumeQueue) and network connections. We recommend setting  655350 for file descriptors.</p></li><li><p><strong>Disk scheduler</strong>, the deadline I&#x2F;O scheduler is recommended for RocketMQ, which attempts to provide a guaranteed latency for requests.</p></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;The-system-configuration&quot;&gt;&lt;a href=&quot;#The-system-configuration&quot; class=&quot;headerlink&quot; title=&quot;The system configuration&quot;&gt;&lt;/a&gt;The system con</summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="The system configuration" scheme="https://nydia.github.io/tags/The-system-configuration/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ Client Configuration</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/en/Configuration_Client/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/en/Configuration_Client/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T12:59:28.499Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Client-Configuration"><a href="#Client-Configuration" class="headerlink" title="Client Configuration"></a>Client Configuration</h2><p>  Relative to RocketMQ’s Broker cluster, producers and consumers are client. In this section, it mainly describes the common behavior configuration of producers and consumers.<br>​ </p><h3 id="1-Client-Addressing-mode"><a href="#1-Client-Addressing-mode" class="headerlink" title="1 Client Addressing mode"></a>1 Client Addressing mode</h3><figure class="highlight plaintext"><figcaption><span>can let client find the ```Name Server```, and then find the ```Broker```by the ```Name Server```. Followings show a variety of configurations, and priority level from highly to lower, the highly priority configurations can override the lower priority configurations.</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">-  Specified ```Name Server``` address in the code, and multiple ```Name Server``` addresses are separated by semicolons</span><br><span class="line"></span><br><span class="line">```java</span><br><span class="line">producer.setNamesrvAddr(&quot;192.168.0.1:9876;192.168.0.2:9876&quot;);  </span><br><span class="line"></span><br><span class="line">consumer.setNamesrvAddr(&quot;192.168.0.1:9876;192.168.0.2:9876&quot;);</span><br></pre></td></tr></table></figure><ul><li>Specified <code>Name Server</code> address in the Java setup parameters</li></ul><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-Drocketmq.namesrv.addr=192.168.0.1:9876;192.168.0.2:9876  </span><br></pre></td></tr></table></figure><ul><li>Specified <code>Name Server</code> address in the envionment variables</li></ul><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">export   NAMESRV_ADDR=192.168.0.1:9876;192.168.0.2:9876   </span><br></pre></td></tr></table></figure><ul><li>HTTP static server addressing(default)</li></ul><p>After client started, it will access a http static server address, as: <a href="http://jmenv.tbsite.net:8080/rocketmq/nsaddr">http://jmenv.tbsite.net:8080/rocketmq/nsaddr</a>, this URL return the following contents:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">192.168.0.1:9876;192.168.0.2:9876   </span><br></pre></td></tr></table></figure><p>By default, the client accesses the HTTP server every 2 minutes, and update the local Name Server address.The URL is hardcoded in the code, you can change the target server by updating <code>/etc/hosts</code> file, such as add following configuration at the <code>/etc/hosts</code>:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">10.232.22.67    jmenv.taobao.net   </span><br></pre></td></tr></table></figure><p>HTTP static server addressing is recommended, because it is simple client deployment, and the Name Server cluster can be upgraded hot.</p><h3 id="2-Client-Configuration"><a href="#2-Client-Configuration" class="headerlink" title="2 Client Configuration"></a>2 Client Configuration</h3><p><code>DefaultMQProducer</code>,<code>TransactionMQProducer</code>,<code>DefaultMQPushConsumer</code>,<code>DefaultMQPullConsumer</code> all extends the <code>ClientConfig</code> Class, <code>ClientConfig</code> as the client common configuration class. Client configuration style like getXXX,setXXX, each of the parameters can config by spring and also config their in the code. Such as the <code>namesrvAddr</code> parameter: <code>producer.setNamesrvAddr(&quot;192.168.0.1:9876&quot;)</code>, same with the other parameters.</p><h4 id="2-1-Client-Common-Configuration"><a href="#2-1-Client-Common-Configuration" class="headerlink" title="2.1 Client Common Configuration"></a>2.1 Client Common Configuration</h4><table><thead><tr><th>Pamater Name</th><th>Default Value</th><th>Description</th></tr></thead><tbody><tr><td>namesrvAddr</td><td></td><td>Name Server address list, multiple NameServer addresses are separated by semicolons</td></tr><tr><td>clientIP</td><td>local IP</td><td>Client local ip address, some machines will fail to recognize the client IP address, which needs to be enforced in the code</td></tr><tr><td>instanceName</td><td>DEFAULT</td><td>Name of the client instance, Multiple producers and consumers created by the client actually share one internal instance (this instance contains network connection, thread resources, etc.).</td></tr><tr><td>clientCallbackExecutorThreads</td><td>4</td><td>Number of communication layer asynchronous callback threads</td></tr><tr><td>pollNameServerInteval</td><td>30000</td><td>Polling the Name Server interval in milliseconds</td></tr><tr><td>heartbeatBrokerInterval</td><td>30000</td><td>The heartbeat interval, in milliseconds, is sent to the Broker</td></tr><tr><td>persistConsumerOffsetInterval</td><td>5000</td><td>The persistent Consumer consumes the progress interval in milliseconds</td></tr></tbody></table><h4 id="2-2-Producer-Configuration"><a href="#2-2-Producer-Configuration" class="headerlink" title="2.2 Producer Configuration"></a>2.2 Producer Configuration</h4><table><thead><tr><th>Pamater Name</th><th>Default Value</th><th>Description</th></tr></thead><tbody><tr><td>producerGroup</td><td>DEFAULT_PRODUCER</td><td>The name of the Producer group. If multiple producers belong to one application and send the same message, they should be grouped into the same group</td></tr><tr><td>createTopicKey</td><td>TBW102</td><td>When a message is sent, topics that do not exist on the server are automatically created and a Key is specified that can be used to configure the default route to the topic where the message is sent.</td></tr><tr><td>defaultTopicQueueNums</td><td>4</td><td>The number of default queue when sending messages and auto created topic which not exists the server</td></tr><tr><td>sendMsgTimeout</td><td>10000</td><td>Timeout time of sending message in milliseconds</td></tr><tr><td>compressMsgBodyOverHowmuch</td><td>4096</td><td>The message Body begins to compress beyond the size(the Consumer gets the message automatically unzipped.), unit of byte</td></tr><tr><td>retryAnotherBrokerWhenNotStoreOK</td><td>FALSE</td><td>If send message and return sendResult but sendStatus!&#x3D;SEND_OK, Whether to resend</td></tr><tr><td>retryTimesWhenSendFailed</td><td>2</td><td>If send message failed, maximum number of retries, this parameter only works for synchronous send mode</td></tr><tr><td>maxMessageSize</td><td>4MB</td><td>Client limit message size, over it may error. Server also limit so need to work with server</td></tr><tr><td>transactionCheckListener</td><td></td><td>The transaction message looks back to the listener, if you want send transaction message, you must setup this</td></tr><tr><td>checkThreadPoolMinSize</td><td>1</td><td>Minimum of thread in thread pool when Broker look back Producer transaction status</td></tr><tr><td>checkThreadPoolMaxSize</td><td>1</td><td>Maximum of thread in thread pool when Broker look back Producer transaction status</td></tr><tr><td>checkRequestHoldMax</td><td>2000</td><td>Producer local buffer request queue size when Broker look back Producer transaction status</td></tr><tr><td>RPCHook</td><td>null</td><td>This parameter is passed in when the Producer is creating, including the pre-processing before the message sending and the processing after the message response. The user can do some security control or other operations in the first interface.</td></tr></tbody></table><h4 id="2-3-PushConsumer-Configuration"><a href="#2-3-PushConsumer-Configuration" class="headerlink" title="2.3 PushConsumer Configuration"></a>2.3 PushConsumer Configuration</h4><table><thead><tr><th>Pamater Name</th><th>Default Value</th><th>Description</th></tr></thead><tbody><tr><td>consumerGroup</td><td>DEFAULT_CONSUMER</td><td>Consumer group name. If multi Consumer belong to an application, subscribe the same message and consume logic as the same, they should be gathered together</td></tr><tr><td>messageModel</td><td>CLUSTERING</td><td>Message support two mode: cluster consumption and broadcast consumption</td></tr><tr><td>consumeFromWhere</td><td>CONSUME_FROM_LAST_OFFSET</td><td>After Consumer started, default consumption from last location, it include two situation: One is last consumption location is not expired, and consumption start at last location; The other is last location expired, start consumption at current queue’s first message</td></tr><tr><td>consumeTimestamp</td><td>Half an hour ago</td><td>Only consumeFromWhere&#x3D;CONSUME_FROM_TIMESTAMP, this can work</td></tr><tr><td>allocateMessageQueueStrategy</td><td>AllocateMessageQueueAveragely</td><td>Implements strategy of Rebalance algorithms</td></tr><tr><td>subscription</td><td></td><td>subscription relation</td></tr><tr><td>messageListener</td><td></td><td>message listener</td></tr><tr><td>offsetStore</td><td></td><td>Consumption progress store</td></tr><tr><td>consumeThreadMin</td><td>10</td><td>Minimum of thread in consumption thread pool</td></tr><tr><td>consumeThreadMax</td><td>20</td><td>Maximum of thread in consumption thread pool</td></tr><tr><td></td><td></td><td></td></tr><tr><td>consumeConcurrentlyMaxSpan</td><td>2000</td><td>Maximum span allowed for single queue parallel consumption</td></tr><tr><td>pullThresholdForQueue</td><td>1000</td><td>Pull message local queue cache maximum number of messages</td></tr><tr><td>pullInterval</td><td>0</td><td>Pull message interval, because long polling it is 0, but for flow control, you can set value which greater than 0 in milliseconds</td></tr><tr><td>consumeMessageBatchMaxSize</td><td>1</td><td>Batch consume message</td></tr><tr><td>pullBatchSize</td><td>32</td><td>Batch pull message</td></tr></tbody></table><h4 id="2-4-PullConsumer-Configuration"><a href="#2-4-PullConsumer-Configuration" class="headerlink" title="2.4 PullConsumer Configuration"></a>2.4 PullConsumer Configuration</h4><table><thead><tr><th>Pamater Name</th><th>Default Value</th><th>Description</th></tr></thead><tbody><tr><td>consumerGroup</td><td>DEFAULT_CONSUMER</td><td>Consumer group name. If multi Consumer belong to an application, subscribe the same message and consume logic as the same, they should be gathered together</td></tr><tr><td>brokerSuspendMaxTimeMillis</td><td>20000</td><td>Long polling, Consumer pull message request suspended for the longest time in the Broker in milliseconds</td></tr><tr><td>consumerTimeoutMillisWhenSuspend</td><td>30000</td><td>Long polling, Consumer pull message request suspend in the Broker over this time value, client think timeout. Unit is milliseconds</td></tr><tr><td>consumerPullTimeoutMillis</td><td>10000</td><td>Not long polling, timeout time of pull message in milliseconds</td></tr><tr><td>messageModel</td><td>BROADCASTING</td><td>Message support two mode: cluster consumption and broadcast consumption</td></tr><tr><td>messageQueueListener</td><td></td><td>Listening changing of queue</td></tr><tr><td>offsetStore</td><td></td><td>Consumption schedule store</td></tr><tr><td>registerTopics</td><td></td><td>Collection of registered topics</td></tr><tr><td>allocateMessageQueueStrategy</td><td>AllocateMessageQueueAveragely</td><td>Implements strategy about Rebalance algorithm</td></tr></tbody></table><h4 id="2-5-Message-Data-Structure"><a href="#2-5-Message-Data-Structure" class="headerlink" title="2.5 Message Data Structure"></a>2.5 Message Data Structure</h4><table><thead><tr><th>Field Name</th><th>Default Value</th><th>Description</th></tr></thead><tbody><tr><td>Topic</td><td>null</td><td>Required, the name of the topic to which the message belongs</td></tr><tr><td>Body</td><td>null</td><td>Required, message body</td></tr><tr><td>Tags</td><td>null</td><td>Optional, message tag, convenient for server filtering. Currently only one tag per message is supported</td></tr><tr><td>Keys</td><td>null</td><td>Optional, represent this message’s business keys, server create hash indexes based keys. After setting, you can find message by <code>Topics</code>,<code>Keys</code> in Console system. Because of hash indexes, please make key as unique as possible, such as order number, goods Id and so on.</td></tr><tr><td>Flag</td><td>0</td><td>Optional, it is entirely up to the application, and RocketMQ does not intervene</td></tr><tr><td>DelayTimeLevel</td><td>0</td><td>Optional, message delay level, 0 represent no delay, greater tan 0 can consume</td></tr><tr><td>WaitStoreMsgOK</td><td>TRUE</td><td>Optional, indicates whether the message is not answered until the server is down.</td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Client-Configuration&quot;&gt;&lt;a href=&quot;#Client-Configuration&quot; class=&quot;headerlink&quot; title=&quot;Client Configuration&quot;&gt;&lt;/a&gt;Client Configuration&lt;/h2&gt;&lt;</summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="Client Configuration" scheme="https://nydia.github.io/tags/Client-Configuration/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ message</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/en/Design_Remoting/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/en/Design_Remoting/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T13:01:25.591Z</updated>
    
    <content type="html"><![CDATA[<p>RocketMQ message queue cluster mainly includes four roles: NameServer, Broker (Master&#x2F;Slave), Producer and Consumer. The basic communication process is as follows:<br>(1) After Broker start-up, it needs to complete one operation: register itself to NameServer, and then report Topic routing information to NameServer at regular intervals of 30 seconds.<br>(2) When message Producer sends a message as a client, it needs to obtain routing information from the local cache TopicPublishInfoTable according to the Topic of the message. If not, it will be retrieved from NameServer and update to local cache, at the same time, Producer will retrieve routing information from NameServer every 30 seconds by default.<br>(3) Message Producer chooses a queue to send the message according to the routing information obtained in 2); Broker receives the message and records it in disk as the receiver of the message.<br>(4) After message Consumer gets the routing information according to 2) and complete the load balancing of the client, then select one or several message queues to pull messages and consume them.</p><p>From 1) ~ 3) above, we can see that both Producer, Broker and NameServer communicate with each other(only part of MQ communication is mentioned here), so how to design a good network communication module is very important in MQ. It will determine the overall messaging capability and final performance of the RocketMQ cluster.</p><p>rocketmq-remoting module is the module responsible for network communication in RocketMQ message queue. It is relied on and referenced by almost all other modules (such as rocketmq-client,rocketmq-broker,rocketmq-namesrv) that need network communication. In order to realize the efficient data request and reception between the client and the server, the RocketMQ message queue defines the communication protocol and extends the communication module on the basis of Netty.</p><h3 id="1-Remoting-Communication-Class-Structure"><a href="#1-Remoting-Communication-Class-Structure" class="headerlink" title="1 Remoting Communication Class Structure"></a>1 Remoting Communication Class Structure</h3><p><img src="https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_3.png"></p><h3 id="2-Protocol-Design-and-Code"><a href="#2-Protocol-Design-and-Code" class="headerlink" title="2 Protocol Design and Code"></a>2 Protocol Design and Code</h3><p>When a message is sent between Client and Server, a protocol convention is needed for the message sent, so it is necessary to customize the message protocol of RocketMQ. At the same time, in order to efficiently transmit messages and read the received messages, it is necessary to encode and decode the messages. In RocketMQ, the RemotingCommand class encapsulates all data content in the process of message transmission, which includes not only all data structures, but also encoding and decoding operations.</p><table><thead><tr><th>Header field</th><th>Type</th><th>Request desc</th><th>Response desc</th></tr></thead><tbody><tr><td>code</td><td>int</td><td>Request  code. answering business processing is different according to different requests code</td><td>Response code. 0 means success, and non-zero means errors.</td></tr><tr><td>language</td><td>LanguageCode</td><td>Language implemented by the requester</td><td>Language implemented by the responder</td></tr><tr><td>version</td><td>int</td><td>Version of Request Equation</td><td>Version of Response Equation</td></tr><tr><td>opaque</td><td>int</td><td>Equivalent to reqeustId, the different request identification codes on the same connection correspond to those in the response message</td><td>The response returns directly without modification</td></tr><tr><td>flag</td><td>int</td><td>Sign, used to distinguish between ordinary RPC or oneway RPC</td><td>Sign, used to distinguish between ordinary RPC or oneway RPC</td></tr><tr><td>remark</td><td>String</td><td>Transfer custom text information</td><td>Transfer custom text information</td></tr><tr><td>extFields</td><td>HashMap&lt;String, String&gt;</td><td>Request custom extension information</td><td>Response custom extension information</td></tr><tr><td><img src="https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_4.png"></td><td></td><td></td><td></td></tr><tr><td>From the above figure, the transport content can be divided into four parts:</td><td></td><td></td><td></td></tr></tbody></table><p> (1) Message length: total length, four bytes of storage, occupying an int type; </p><p>(2) Serialization type header length: occupying an int type. The first byte represents the serialization type, and the last three bytes represent the header length；</p><p>(3) Header data: serialized header data;</p><p>(4) Message body data: binary byte data content of message body;</p><h3 id="3-Message-Communication-Mode-and-Procedure"><a href="#3-Message-Communication-Mode-and-Procedure" class="headerlink" title="3 Message Communication Mode and Procedure"></a>3 Message Communication Mode and Procedure</h3><p>There are three main ways to support communication in RocketMQ message queue: synchronous (sync), asynchronous (async), one-way (oneway). The “one-way” communication mode is relatively simple and is generally used in sending heartbeat packets without paying attention to its Response. Here, mainly introduce the asynchronous communication flow of RocketMQ.<br><img src="https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_5.png"></p><h3 id="4-Reactor-Multithread-Design"><a href="#4-Reactor-Multithread-Design" class="headerlink" title="4 Reactor Multithread Design"></a>4 Reactor Multithread Design</h3><p>The RPC communication of RocketMQ uses Netty component as the underlying communication library, and also follows the Reactor multithread model. At the same time, some extensions and optimizations are made on it.<br><img src="https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_6.png"><br>Above block diagram can roughly understand the Reactor multi-thread model of NettyRemotingServer in RocketMQ. A Reactor main thread (eventLoopGroupBoss, is 1 above) is responsible for listening to TCP network connection requests, establishing connections, creating SocketChannel, and registering on selector. The source code of RocketMQ automatically selects NIO and Epoll according to the type of OS. Then listen to real network data. After you get the network data, you throw it to the Worker thread pool (eventLoopGroupSelector, is the “N” above, the default is 3 in the source code). You need to do SSL verification, codec, idle check, network connection management before you really execute the business logic. These tasks to defaultEventExecutorGroup (that is, “M1” above, the default set to 8 in the source code) to do. The processing business operations are executed in the business thread pool. According to the RomotingCommand business request code, the corresponding processor is found in the processorTable local cache variable and encapsulated into the task, and then submitted to the corresponding business processor processing thread pool for execution (sendMessageExecutor,). Take sending a message, for example, the “M2” above. The thread pool continues to increase in several steps from entry to business logic, which is related to the complexity of each step. The more complex the thread pool is, the wider the concurrent channel is required.</p><table><thead><tr><th>Number of thread</th><th>Name of thread</th><th>Desc of thread</th></tr></thead><tbody><tr><td>1</td><td>NettyBoss_%d</td><td>Reactor Main thread</td></tr><tr><td>N</td><td>NettyServerEPOLLSelector_%d_%d</td><td>Reactor thread pool</td></tr><tr><td>M1</td><td>NettyServerCodecThread_%d</td><td>Worker thread pool</td></tr><tr><td>M2</td><td>RemotingExecutorThread_%d</td><td>bussiness processor thread pool</td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;RocketMQ message queue cluster mainly includes four roles: NameServer, Broker (Master&amp;#x2F;Slave), Producer and Consumer. The basic commu</summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="RocketMQ message" scheme="https://nydia.github.io/tags/RocketMQ-message/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ Message Queries</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/en/Design_Query/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/en/Design_Query/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T13:01:02.635Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Message-Queries"><a href="#Message-Queries" class="headerlink" title="Message Queries"></a>Message Queries</h1><p>RocketMQ supports message queries by two dimensions, which are “Query Message by Message Id” and “Query Message by Message Key”.</p><h2 id="1-Query-Message-by-Message-Id"><a href="#1-Query-Message-by-Message-Id" class="headerlink" title="1. Query Message by Message Id"></a>1. Query Message by Message Id</h2><p>The MessageId in RocketMQ has a total length of 16 bytes, including the broker address (IP address and port) and CommitLog offset. In RocketMQ, the specific approach is that the Client resolves the Broker’s address (IP address and port) and the CommitLog’s offset address from the MessageId. Then both of them are encapsulated into an RPC request, and finally it will be sent through the communication layer (business request code: VIEW_MESSAGE_BY_ID). The Broker reads a message by using the CommitLog offset and size to find the real message in the CommitLog and then return, which is how QueryMessageProcessor works.</p><h2 id="2-Query-Message-by-Message-Id"><a href="#2-Query-Message-by-Message-Id" class="headerlink" title="2. Query Message by Message Id"></a>2. Query Message by Message Id</h2><p>“Query Messages by Message Key” is mainly based on RocketMQ’s IndexFile. The logical structure of the IndexFile is similar to the implementation of HashMap in JDK. The specific structure of the IndexFile is as follows:</p><p><img src="/images/rocketmq_design_message_query.png"></p><p>The IndexFile provides the user with the querying service by “Querying Messages by Message Key”. The IndexFile is stored in $HOME\store\index${fileName}, and the file name is named after the timestamp at the time of creation. The file size is fixed, which is 420,000,040 bytes (40+5million*4+20million*20). If the UNIQ_KEY is set in the properties of the message, then the “topic + ‘#’ + UNIQ_KEY” will be used as the index. Likewise, if the KEYS is set in the properties of the message (multiple KEYs should be separated by spaces), then the “topic + ‘#’ + KEY” will be used as the index.</p><p>The index data contains four fields, Key Hash, CommitLog offset, Timestamp and NextIndex offset, for a total of 20 Bytes. The NextIndex offset of the index data will point to the previous index data if the Key Hash of the index data is the same as that of the previous index data. If a hash conflict occurs, then the NextIndex offset can be used as the field to string all conflicting indexes in a linked list. What the Timestamp records is the time difference between two storeTimestamps, instead of a specific time. The structure of the entire IndexFile is shown in the graph. The Header is used to store some general statistics, which needs 40 bytes. The Slot Table of 4*5million bytes does not save the real index data, but saves the header of the singly linked list corresponding to each slot. The Index Linked List of 20*20million is the real index data, that is, an Index File can hold 20million indexes.</p><p>The specific method of “Query Message by Message Key” is that the topic and message key are used to find the record in the IndexFile, and then read the message from the file of CommitLog according to the CommitLog offset in this record.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Message-Queries&quot;&gt;&lt;a href=&quot;#Message-Queries&quot; class=&quot;headerlink&quot; title=&quot;Message Queries&quot;&gt;&lt;/a&gt;Message Queries&lt;/h1&gt;&lt;p&gt;RocketMQ supports </summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="Message Queries" scheme="https://nydia.github.io/tags/Message-Queries/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ Message Storage</title>
    <link href="https://nydia.github.io/2022/10/01/rocketmq-docs/en/Design_Store/"/>
    <id>https://nydia.github.io/2022/10/01/rocketmq-docs/en/Design_Store/</id>
    <published>2022-10-01T09:52:56.000Z</published>
    <updated>2025-04-26T13:01:36.932Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Message-Storage"><a href="#Message-Storage" class="headerlink" title="Message Storage"></a>Message Storage</h1><p><img src="/images/rocketmq_storage_arch.png"></p><p>Message storage is the most complicated and important part of RocketMQ. This section will describe the three aspects of RocketMQ:</p><ul><li>Message storage architecture</li><li>PageCache and memory mapping</li><li>RocketMQ’s two different disk flushing methods.</li></ul><h2 id="1-Message-Storage-Architecture"><a href="#1-Message-Storage-Architecture" class="headerlink" title="1 Message Storage Architecture"></a>1 Message Storage Architecture</h2><p>The message storage architecture diagram consists of 3 files related to message storage: <code>CommitLog</code> file, <code>ConsumeQueue</code> file, and <code>IndexFile</code>.</p><ul><li><code>CommitLog</code>：The <code>CommitLog</code> file stores message body and metadata sent by producer, and the message content is not fixed length. The default size of one <code>CommitLog</code> file is 1G, the length of the file name is 20 digits, the left side is zero padded, and the remaining is the starting offset. For example, <code>00000000000000000000</code> represents the first file, the starting offset is 0, and the file size is 1G&#x3D;1073741824, when the first <code>CommitLog</code> file is full, the second <code>CommitLog</code> file is <code>00000000001073741824</code>, the starting offset is 1073741824, and so on. The message is mainly appended to the log file sequentially. When one <code>CommitLog</code> file is full, the next will be written.</li><li><code>ConsumeQueue</code>: The <code>ConsumeQueue</code> is used to improve the performance of message consumption. Since RocketMQ uses topic-based subscription mode, message consumption is specific to the topic. Traversing the commitlog file to retrieve messages of one topic is very inefficient. The consumer can find the messages to be consumed according to the <code>ConsumeQueue</code>. The <code>ConsumeQueue</code>(logic consume queue) as an index of the consuming message stores the starting physical offset <code>offset</code> in <code>CommitLog</code> of the specified topic, the message size <code>size</code> and the hash code of the message tag. The <code>ConsumeQueue</code> file can be regarded as a topic-based <code>CommitLog</code> index file, so the consumequeue folder is organized as follows: <code>topic/queue/file</code> three-layer organization structure, the specific storage path is <code>$HOME/store/consumequeue/&#123;topic&#125;/&#123;queueId &#125;/&#123;fileName&#125;</code>. The consumequeue file uses a fixed-length design, each entry occupies 20 bytes, which is an 8-byte commitlog physical offset, a 4-byte message length, and an 8-byte tag hashcode. One consumequeue file consists of 0.3 million entries, each entry can be randomly accessed like an array, each <code>ConsumeQueue</code> file’s size is about 5.72MB.</li><li><code>IndexFile</code>: The <code>IndexFile</code> provides a way to query messages by key or time interval. The path of the <code>IndexFile</code> is <code>$HOME/store/index/$&#123;fileName&#125;</code>, the file name <code>fileName</code> is named after the timestamp when it was created. One IndexFile’s size is about 400M, and it can store 2000W indexes. The underlying storage of <code>IndexFile</code> is designed to implement the <code>HashMap</code> structure in the file system, so RocketMQ’s index file is a hash index.</li></ul><p>From the above architecture of the RocketMQ message storage, we can see RocketMQ uses a hybrid storage structure, that is, all the queues in an instance of the broker share a single log file <code>CommitLog</code> to store messages. RocketMQ’s hybrid storage structure(messages of multiple topics are stored in one CommitLog) uses a separate storage structure for the data and index parts for Producer and Consumer respectively. The Producer sends the message to the Broker, then the Broker persists the message to the CommitLog file synchronously or asynchronously. As long as the message is persisted to the CommitLog on the disk, the message sent by the Producer will not be lost. Because of this, Consumer will definitely have the opportunity to consume this message. When no message can be pulled, the consumer can wait for the next pull. And the server also supports the long polling mode: if a pull request pulls no messages, the Broker can wait for 30 seconds, as long as new message arrives in this interval, it will be returned directly to the consumer. Here, RocketMQ’s specific approach is using Broker’s background service thread <code>ReputMessageService</code> to continuously dispatch requests and asynchronously build ConsumeQueue (Logical Queue) and IndexFile data.</p><h2 id="2-PageCache-and-Memory-Map"><a href="#2-PageCache-and-Memory-Map" class="headerlink" title="2 PageCache and Memory Map"></a>2 PageCache and Memory Map</h2><p>PageCache is a cache of files by the operating system to speed up the reading and writing of files. In general, the speed of sequential read and write files is almost the same as the speed of read and write memory. The main reason is that the OS uses a portion of the memory as PageCache to optimize the performance of the read and write operations. For data writing, the OS will first write to the Cache, and then the <code>pdflush</code> kernel thread asynchronously flush the data in the Cache to the physical disk. For data reading, if it can not hit the page cache when reading a file at a time, the OS will read the file from the physical disk and prefetch the data files of other neighboring blocks sequentially.</p><p>In RocketMQ, the logic consumption queue <code>ConsumeQueue</code> stores less data and is read sequentially. With the help of prefetch of the page cache mechanism, the read performance of the <code>ConsumeQueue</code> file is almost close to the memory read, even in the case of message accumulation, it does not affect performance. But for the log data file <code>CommitLog</code>, it will generate many random access reads when reading the message content, which seriously affects the performance. If you choose the appropriate IO scheduling algorithm, such as setting the IO scheduling algorithm to “Deadline” (when the block storage uses SSD), the performance of random reads will also be improved.</p><p>In addition, RocketMQ mainly reads and writes files through <code>MappedByteBuffer</code>. <code>MappedByteBuffer</code> uses the <code>FileChannel</code> model in NIO to directly map the physical files on the disk to the memory address in user space (<code>Mmap</code> method reduces the performance overhead of traditional IO copying disk file data back and forth between the buffer in kernel space and the buffer in user space), it converts the file operation into direct memory address manipulation, which greatly improves the efficiency of reading and writing files (Because of the need to use the memory mapping mechanism, RocketMQ’s file storage is fixed-length, making it easy to map the entire file to memory at a time).</p><h2 id="3-Message-Disk-Flush"><a href="#3-Message-Disk-Flush" class="headerlink" title="3 Message Disk Flush"></a>3 Message Disk Flush</h2><p><img src="/images/rocketmq_storage_flush.png"></p><ul><li>synchronous flush: As shown above, the RocketMQ’s Broker will return a successful <code>ACK</code> response to the Producer after the message is truly persisted to disk. Synchronous flushing is a good guarantee for the reliability of MQ messages, but it will have a big impact on performance. Generally, it is suitable for financial business applications.</li><li>asynchronous flush: Asynchronous flushing can take full advantage of the PageCache of the OS, as long as the message is written to the PageCache, the successful <code>ACK</code> can be returned to the Producer. The message flushing is performed by the background asynchronous thread, which reduces the read and write delay and improves the performance and throughput of the MQ.</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Message-Storage&quot;&gt;&lt;a href=&quot;#Message-Storage&quot; class=&quot;headerlink&quot; title=&quot;Message Storage&quot;&gt;&lt;/a&gt;Message Storage&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;/images/</summary>
      
    
    
    
    <category term="RocketMQ" scheme="https://nydia.github.io/categories/RocketMQ/"/>
    
    
    <category term="Message Storage" scheme="https://nydia.github.io/tags/Message-Storage/"/>
    
  </entry>
  
</feed>
