<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.0">Jekyll</generator><link href="https://ronaldoshuang.github.io//feed.xml" rel="self" type="application/atom+xml" /><link href="https://ronaldoshuang.github.io//" rel="alternate" type="text/html" /><updated>2021-10-16T10:12:42+00:00</updated><id>https://ronaldoshuang.github.io//feed.xml</id><title type="html">我是李香兰小朋友</title><subtitle>我是李香兰小朋友的个人博客</subtitle><author><name>Qing Shuang</name></author><entry><title type="html">My 0</title><link href="https://ronaldoshuang.github.io//2021/10/16/my-0/" rel="alternate" type="text/html" title="My 0" /><published>2021-10-16T00:00:00+00:00</published><updated>2021-10-16T00:00:00+00:00</updated><id>https://ronaldoshuang.github.io//2021/10/16/my-0</id><content type="html" xml:base="https://ronaldoshuang.github.io//2021/10/16/my-0/">&lt;h1 id=&quot;1你经常访问的技术类论坛有哪些请推荐三篇你最近读到对你很有帮助的技术类帖子列出帖子的标题和url链接&quot;&gt;&lt;strong&gt;1.你经常访问的技术类论坛有哪些？请推荐三篇你最近读到对你很有帮助的技术类帖子，列出帖子的标题和URL链接。&lt;/strong&gt;&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;如下图看博客看的比较多，最近都是在看书，刚看完了Spring Cloud微服务实战，后续由于工作需要会学习hadoop和spark。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.bmp.ovh/imgs/2021/10/b98b50b5f529b1d5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;2你在github上关注star了哪些项目请列举三个你在github上关注的项目列出项目名称和url链接&quot;&gt;&lt;strong&gt;2.你在Github上关注（star）了哪些项目？请列举三个你在Github上关注的项目，列出项目名称和URL链接。&lt;/strong&gt;&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vue-element-admin：一个vue实现的前端页面，因为公司之前接了一个小项目（保费计算），想要使用vue实现，所以关注了这个项目，实现快速开发。（https://github.com/PanJiaChen/vue-element-admin/blob/master/README.md）

rocketmq：学习rocket的时候，了解了具体的api，然后看了一下底层的源码（https://github.com/apache/rocketmq）

WxJava：之前自己搞了一个公众号，实现自动回复等功能，所以就关注了这个项目，这个项目封装了微信官方提供的api。（https://github.com/Wechat-Group/WxJava）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;3你在github上参与过开源项目吗如果你在某个项目有代码贡献请列举出来列出项目名称和url链接以及你贡献代码的pull-request&quot;&gt;&lt;strong&gt;3.你在Github上参与过开源项目吗？如果你在某个项目有代码贡献，请列举出来，列出项目名称和URL链接以及你贡献代码的Pull Request。&lt;/strong&gt;&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;惭愧，目前还没有在参与官方的项目，不过自己开源了两个项目。
第一个叫转换服务，实现A转成B值。
第二个叫重试组件，因为目前自己负责的项目需要依赖第三方的接口获取数据，但是第三方的接口不稳定，所以会导致我们没法获取到数据，造成我们的系统数据统计会出错，所以自己开发了重试组件，当调第三方出错的时候，会定时重试，直到成功为止。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;4你最近正在学习或者计划学习的技术领域是什么&quot;&gt;&lt;strong&gt;4.你最近正在学习或者计划学习的技术领域是什么？&lt;/strong&gt;&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;因为最近公司需要重新规划产品，需要学习hadoop和spark流计算，这是目前的学习计划，因为自己之前都是纯前后端开发，所以能学习新的领域还是挺兴奋的。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;5请给出如下需求的解决方案或思路需要对数百tb的文件图片视频或其他格式化文件分两种情况一种是单个文件尺寸不大文件数量多一种是单个文件尺寸较大进行存储和管理有如下要求&quot;&gt;&lt;strong&gt;5.请给出如下需求的解决方案或思路：需要对数百TB的文件（图片、视频或其他格式化文件，分两种情况，一种是单个文件尺寸不大，文件数量多；一种是单个文件尺寸较大）进行存储和管理。有如下要求：&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;​		&lt;strong&gt;a) .基于文件名（或存储路径）快速索引访问；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​		&lt;strong&gt;b) 基于文件内容快速索引访问；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​		&lt;strong&gt;c) 动态扩展存储容量，不影响应用层访问接口；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​		&lt;strong&gt;d) 应用层提供对这些文件的管理和访问接口。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;请给出解决方案或者思路，包括：系统整体架构设计，底层架构选择，应用层解决方案。&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;如下方架构图所示。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.bmp.ovh/imgs/2021/10/574c855e9483c029.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;6请回答以下实际开发案例的问题java语言实现基于springcloud框架用户登录授权认证是应用系统种最常见的功能有如下需求&quot;&gt;&lt;strong&gt;6.请回答以下实际开发案例的问题（Java语言实现，基于SpringCloud框架）：用户登录授权认证是应用系统种最常见的功能，有如下需求：&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;​	&lt;strong&gt;a) 用户基于用户名密码进行登录认证；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​	&lt;strong&gt;b) 用户登录成功后系统生成Token，后续所有接口访问基于Token判断是否有权限可以访问相应的接口（不同的接口有不同的权限）。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;请给出以下问题的解决方案或者思路：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​	&lt;strong&gt;a) 如何存储和读取每一个登录用户的Token，每一个访问请求都需要通过Token来判断访问权限，需要确保效率；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​	&lt;strong&gt;b) 基于Token的接口访问权限控制如何设计；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​	&lt;strong&gt;c) 在SpringCloud的框架体系中，如何实现上述功能，需要用到哪些组件；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​	&lt;strong&gt;d) 将Token的存储和访问封装为一个Java类并给出实现代码，至少实现以下接口：插入新的用户Token；删除指定用户Token；根据Token获取用户基本信息（用户名，用户权限等）；检查Token是否有某接口的访问权限。&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;业务流程如下图&quot;&gt;业务流程如下图&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.bmp.ovh/imgs/2021/10/e809ca09c13aa346.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;设计思路&quot;&gt;设计思路：&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1.用户请求发送到服务器，会被Loginfilter拦截器拦截到，判断用户的登陆状态和访问权限。
   A.根据TokenId访问redis服务器，如果没有获取到值，则表示用户没有登陆，跳转到登陆页面，进行登陆。
     a.登陆页面进行登陆，输入用户名和密码进行登录
     b.登陆成功，生成tokenId，并读取用户表和用户资源权限表，并保存到redis中，为map格式 tokenId  url true  url true
   B.根据TokenId访问redis服务器，如果获取到值则表示用户已经登陆，然后进一步判断redis取出来的value中是否包含该请求的url，有则表示有访问权限，没有则跳转到无权限页面。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;创建数据库表&quot;&gt;创建数据库表&lt;/h3&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;创建用户表&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;user&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;                   &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auto_increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;             &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'用户名'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;             &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'密码'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;icon&lt;/span&gt;                 &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'头像'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;nick_name&lt;/span&gt;            &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'昵称'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt;          &lt;span class=&quot;nb&quot;&gt;datetime&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;login_time&lt;/span&gt;           &lt;span class=&quot;nb&quot;&gt;datetime&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'最后登录时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;               &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'帐号启用状态：0-&amp;gt;禁用；1-&amp;gt;启用'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;创建接口资源访问&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;接口地址，接口描述&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;                   &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auto_increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt;          &lt;span class=&quot;nb&quot;&gt;datetime&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;                 &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'资源名称'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;                  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'资源URL'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;          &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'描述'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;创建用户资源权限表：&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_resource_relation&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;                   &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auto_increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;              &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'用户ID'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;resource_id&lt;/span&gt;          &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'资源ID'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;代码实现&quot;&gt;代码实现&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;cm&quot;&gt;/**
 * 用户token  service
 */&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenServiceImpl&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RedisTemplate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 插入新的用户Token
     * @param username
     * @return
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;addUserToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userInfoMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;//获取用户信息&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getUserByUserName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;userInfoMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;info&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toJSONString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;//根据用户id获取该用户的资源权限&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getResourceByUserId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;userInfoMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;resource&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toJSONString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;//根据user信息生成tokenId&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getTokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;opsForHash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;putAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userInfoMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 删除指定用户Token
     * @param userId
     * @return
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;deleteUserToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//获取用户信息&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getUserByUserId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//根据user信息生成tokenId&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getTokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//根据tokenId删除reids的值&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 根据Token获取用户基本信息（用户名，用户权限等）
     * @param tokenId
     * @return
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getUserInfoByTokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//获取用户信息&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;opsForHash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;info&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parseObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//获取用户信息&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;opsForHash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;resource&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parseArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 检查Tokenid和访问接口地址判断是否有某接口的访问权限
     * @param tokenId
     * @param url
     * @return
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isRightByTokenIdAndUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//获取用户权限&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;opsForHash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;resource&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parseArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Qing Shuang</name></author><summary type="html">1.你经常访问的技术类论坛有哪些？请推荐三篇你最近读到对你很有帮助的技术类帖子，列出帖子的标题和URL链接。</summary></entry><entry><title type="html">码云–Java测评题目</title><link href="https://ronaldoshuang.github.io//2021/10/16/my-0/" rel="alternate" type="text/html" title="码云–Java测评题目" /><published>2021-10-16T00:00:00+00:00</published><updated>2021-10-16T00:00:00+00:00</updated><id>https://ronaldoshuang.github.io//2021/10/16/my-0</id><content type="html" xml:base="https://ronaldoshuang.github.io//2021/10/16/my-0/">&lt;h1 id=&quot;1你经常访问的技术类论坛有哪些请推荐三篇你最近读到对你很有帮助的技术类帖子列出帖子的标题和url链接&quot;&gt;&lt;strong&gt;1.你经常访问的技术类论坛有哪些？请推荐三篇你最近读到对你很有帮助的技术类帖子，列出帖子的标题和URL链接。&lt;/strong&gt;&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;如下图看博客看的比较多，最近都是在看书，刚看完了Spring Cloud微服务实战，后续由于工作需要会学习hadoop和spark。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.bmp.ovh/imgs/2021/10/b98b50b5f529b1d5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;2你在github上关注star了哪些项目请列举三个你在github上关注的项目列出项目名称和url链接&quot;&gt;&lt;strong&gt;2.你在Github上关注（star）了哪些项目？请列举三个你在Github上关注的项目，列出项目名称和URL链接。&lt;/strong&gt;&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vue-element-admin：一个vue实现的前端页面，因为公司之前接了一个小项目（保费计算），想要使用vue实现，所以关注了这个项目，实现快速开发。（https://github.com/PanJiaChen/vue-element-admin/blob/master/README.md）

rocketmq：学习rocket的时候，了解了具体的api，然后看了一下底层的源码（https://github.com/apache/rocketmq）

WxJava：之前自己搞了一个公众号，实现自动回复等功能，所以就关注了这个项目，这个项目封装了微信官方提供的api。（https://github.com/Wechat-Group/WxJava）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;3你在github上参与过开源项目吗如果你在某个项目有代码贡献请列举出来列出项目名称和url链接以及你贡献代码的pull-request&quot;&gt;&lt;strong&gt;3.你在Github上参与过开源项目吗？如果你在某个项目有代码贡献，请列举出来，列出项目名称和URL链接以及你贡献代码的Pull Request。&lt;/strong&gt;&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;惭愧，目前还没有在参与官方的项目，不过自己开源了两个项目。
第一个叫转换服务，实现A转成B值。
第二个叫重试组件，因为目前自己负责的项目需要依赖第三方的接口获取数据，但是第三方的接口不稳定，所以会导致我们没法获取到数据，造成我们的系统数据统计会出错，所以自己开发了重试组件，当调第三方出错的时候，会定时重试，直到成功为止。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;4你最近正在学习或者计划学习的技术领域是什么&quot;&gt;&lt;strong&gt;4.你最近正在学习或者计划学习的技术领域是什么？&lt;/strong&gt;&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;因为最近公司需要重新规划产品，需要学习hadoop和spark流计算，这是目前的学习计划，因为自己之前都是纯前后端开发，所以能学习新的领域还是挺兴奋的。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;5请给出如下需求的解决方案或思路需要对数百tb的文件图片视频或其他格式化文件分两种情况一种是单个文件尺寸不大文件数量多一种是单个文件尺寸较大进行存储和管理有如下要求&quot;&gt;&lt;strong&gt;5.请给出如下需求的解决方案或思路：需要对数百TB的文件（图片、视频或其他格式化文件，分两种情况，一种是单个文件尺寸不大，文件数量多；一种是单个文件尺寸较大）进行存储和管理。有如下要求：&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;​		&lt;strong&gt;a) .基于文件名（或存储路径）快速索引访问；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​		&lt;strong&gt;b) 基于文件内容快速索引访问；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​		&lt;strong&gt;c) 动态扩展存储容量，不影响应用层访问接口；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​		&lt;strong&gt;d) 应用层提供对这些文件的管理和访问接口。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;请给出解决方案或者思路，包括：系统整体架构设计，底层架构选择，应用层解决方案。&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;如下方架构图所示。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.bmp.ovh/imgs/2021/10/574c855e9483c029.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;6请回答以下实际开发案例的问题java语言实现基于springcloud框架用户登录授权认证是应用系统种最常见的功能有如下需求&quot;&gt;&lt;strong&gt;6.请回答以下实际开发案例的问题（Java语言实现，基于SpringCloud框架）：用户登录授权认证是应用系统种最常见的功能，有如下需求：&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;​	&lt;strong&gt;a) 用户基于用户名密码进行登录认证；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​	&lt;strong&gt;b) 用户登录成功后系统生成Token，后续所有接口访问基于Token判断是否有权限可以访问相应的接口（不同的接口有不同的权限）。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;请给出以下问题的解决方案或者思路：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​	&lt;strong&gt;a) 如何存储和读取每一个登录用户的Token，每一个访问请求都需要通过Token来判断访问权限，需要确保效率；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​	&lt;strong&gt;b) 基于Token的接口访问权限控制如何设计；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​	&lt;strong&gt;c) 在SpringCloud的框架体系中，如何实现上述功能，需要用到哪些组件；&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​	&lt;strong&gt;d) 将Token的存储和访问封装为一个Java类并给出实现代码，至少实现以下接口：插入新的用户Token；删除指定用户Token；根据Token获取用户基本信息（用户名，用户权限等）；检查Token是否有某接口的访问权限。&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;业务流程如下图&quot;&gt;业务流程如下图&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.bmp.ovh/imgs/2021/10/e809ca09c13aa346.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;设计思路&quot;&gt;设计思路：&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1.用户请求发送到服务器，会被Loginfilter拦截器拦截到，判断用户的登陆状态和访问权限。
   A.根据TokenId访问redis服务器，如果没有获取到值，则表示用户没有登陆，跳转到登陆页面，进行登陆。
     a.登陆页面进行登陆，输入用户名和密码进行登录
     b.登陆成功，生成tokenId，并读取用户表和用户资源权限表，并保存到redis中，为map格式 tokenId  url true  url true
   B.根据TokenId访问redis服务器，如果获取到值则表示用户已经登陆，然后进一步判断redis取出来的value中是否包含该请求的url，有则表示有访问权限，没有则跳转到无权限页面。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;创建数据库表&quot;&gt;创建数据库表&lt;/h3&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;创建用户表&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;user&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;                   &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auto_increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;             &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'用户名'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;             &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'密码'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;icon&lt;/span&gt;                 &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'头像'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;nick_name&lt;/span&gt;            &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'昵称'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt;          &lt;span class=&quot;nb&quot;&gt;datetime&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;login_time&lt;/span&gt;           &lt;span class=&quot;nb&quot;&gt;datetime&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'最后登录时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;               &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'帐号启用状态：0-&amp;gt;禁用；1-&amp;gt;启用'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;创建接口资源访问&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;接口地址，接口描述&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;                   &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auto_increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt;          &lt;span class=&quot;nb&quot;&gt;datetime&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;                 &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'资源名称'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;                  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'资源URL'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;          &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'描述'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;创建用户资源权限表：&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_resource_relation&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;                   &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auto_increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;              &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'用户ID'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;resource_id&lt;/span&gt;          &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'资源ID'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;代码实现&quot;&gt;代码实现&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;cm&quot;&gt;/**
 * 用户token  service
 */&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenServiceImpl&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RedisTemplate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 插入新的用户Token
     * @param username
     * @return
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;addUserToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userInfoMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;//获取用户信息&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getUserByUserName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;userInfoMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;info&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toJSONString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;//根据用户id获取该用户的资源权限&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getResourceByUserId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;userInfoMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;resource&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toJSONString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;//根据user信息生成tokenId&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getTokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;opsForHash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;putAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userInfoMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 删除指定用户Token
     * @param userId
     * @return
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;deleteUserToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//获取用户信息&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getUserByUserId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//根据user信息生成tokenId&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getTokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//根据tokenId删除reids的值&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 根据Token获取用户基本信息（用户名，用户权限等）
     * @param tokenId
     * @return
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getUserInfoByTokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//获取用户信息&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;opsForHash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;info&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parseObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//获取用户信息&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;opsForHash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;resource&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parseArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 检查Tokenid和访问接口地址判断是否有某接口的访问权限
     * @param tokenId
     * @param url
     * @return
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isRightByTokenIdAndUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//获取用户权限&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;redisTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;opsForHash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;resource&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parseArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Qing Shuang</name></author><category term="ARTS" /><summary type="html">1.你经常访问的技术类论坛有哪些？请推荐三篇你最近读到对你很有帮助的技术类帖子，列出帖子的标题和URL链接。</summary></entry><entry><title type="html">Zookeeper的典型场景应用</title><link href="https://ronaldoshuang.github.io//2017/06/03/ZooKeeper-4/" rel="alternate" type="text/html" title="Zookeeper的典型场景应用" /><published>2017-06-03T00:00:00+00:00</published><updated>2017-06-03T00:00:00+00:00</updated><id>https://ronaldoshuang.github.io//2017/06/03/ZooKeeper-4</id><content type="html" xml:base="https://ronaldoshuang.github.io//2017/06/03/ZooKeeper-4/">&lt;h1 id=&quot;基于zookeeper实现统一配置管理&quot;&gt;基于zookeeper实现统一配置管理&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/u011320740/article/details/78742625&quot; title=&quot;《基于zookeeper实现统一配置管理》&quot;&gt;https://blog.csdn.net/u011320740/article/details/78742625&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;基于zookeeper的服务注册与发现&quot;&gt;基于Zookeeper的服务注册与发现&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;http://mobile.51cto.com/news-502394.htm&quot; title=&quot;《基于Zookeeper的服务注册与发现》&quot;&gt;http://mobile.51cto.com/news-502394.htm&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;基于zookeeper的分布式锁&quot;&gt;基于Zookeeper的分布式锁&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://www.tuicool.com/articles/VZJr6fY&quot; title=&quot;《基于Zookeeper的分布式锁》&quot;&gt;https://www.tuicool.com/articles/VZJr6fY&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;利用zookeeper实现分布式leader节点选举&quot;&gt;利用zookeeper实现分布式leader节点选举&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/johnson_moon/article/details/78809995&quot; title=&quot;《利用zookeeper实现分布式leader节点选举》&quot;&gt;https://blog.csdn.net/johnson_moon/article/details/78809995&lt;/a&gt;&lt;/p&gt;</content><author><name>Qing Shuang</name></author><category term="Zookeeper" /><summary type="html">基于zookeeper实现统一配置管理</summary></entry><entry><title type="html">Zab：Zookeeper 中的分布式一致性协议介绍</title><link href="https://ronaldoshuang.github.io//2017/06/03/ZooKeeper-5/" rel="alternate" type="text/html" title="Zab：Zookeeper 中的分布式一致性协议介绍" /><published>2017-06-03T00:00:00+00:00</published><updated>2017-06-03T00:00:00+00:00</updated><id>https://ronaldoshuang.github.io//2017/06/03/ZooKeeper-5</id><content type="html" xml:base="https://ronaldoshuang.github.io//2017/06/03/ZooKeeper-5/">&lt;h1 id=&quot;zabzookeeper-中的分布式一致性协议介绍&quot;&gt;Zab：Zookeeper 中的分布式一致性协议介绍&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://www.jianshu.com/p/fb527a64deee&quot; title=&quot;Zab：Zookeeper 中的分布式一致性协议介绍&quot;&gt;https://www.jianshu.com/p/fb527a64deee&lt;/a&gt;&lt;/p&gt;</content><author><name>Qing Shuang</name></author><category term="Zookeeper" /><summary type="html">Zab：Zookeeper 中的分布式一致性协议介绍</summary></entry><entry><title type="html">ZkClient操作Zookeeper</title><link href="https://ronaldoshuang.github.io//2017/06/02/ZooKeeper-2/" rel="alternate" type="text/html" title="ZkClient操作Zookeeper" /><published>2017-06-02T00:00:00+00:00</published><updated>2017-06-02T00:00:00+00:00</updated><id>https://ronaldoshuang.github.io//2017/06/02/ZooKeeper-2</id><content type="html" xml:base="https://ronaldoshuang.github.io//2017/06/02/ZooKeeper-2/">&lt;h1 id=&quot;增删改查节点&quot;&gt;增删改查节点&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.zkclient.base;

import java.util.List;
import java.util.concurrent.TimeUnit;

import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.ZkConnection;

public class ZkClientBase {

	/** zookeeper地址 */
	static final String CONNECT_ADDR = &quot;192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181&quot;;
	/** session超时时间 */
	static final int SESSION_OUTTIME = 5000;//ms 
	
	
	public static void main(String[] args) throws Exception {
		ZkClient zkc = new ZkClient(new ZkConnection(CONNECT_ADDR), 5000);
		//1. create and delete方法 
		zkc.createEphemeral(&quot;/temp&quot;);
		zkc.createPersistent(&quot;/super/c1&quot;, true);
		Thread.sleep(10000);
		zkc.delete(&quot;/temp&quot;);
		zkc.deleteRecursive(&quot;/super&quot;);
		
		//2. 设置path和data 并且读取子节点和每个节点的内容
//		zkc.createPersistent(&quot;/super&quot;, &quot;1234&quot;);
//		zkc.createPersistent(&quot;/super/c1&quot;, &quot;c1内容&quot;);
//		zkc.createPersistent(&quot;/super/c2&quot;, &quot;c2内容&quot;);
//		List&amp;lt;String&amp;gt; list = zkc.getChildren(&quot;/super&quot;);
//		for(String p : list){
//			System.out.println(p);
//			String rp = &quot;/super/&quot; + p;
//			String data = zkc.readData(rp);
//			System.out.println(&quot;节点为：&quot; + rp + &quot;，内容为: &quot; + data);
//		}
		
		//3. 更新和判断节点是否存在
//		zkc.writeData(&quot;/super/c1&quot;, &quot;新内容&quot;);
//		System.out.println(zkc.readData(&quot;/super/c1&quot;));
//		System.out.println(zkc.exists(&quot;/super/c1&quot;));
		
		//4.递归删除/super内容
//		zkc.deleteRecursive(&quot;/super&quot;);		
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;zkclient的watcher&quot;&gt;zkclient的watcher&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.zkclient.watcher;

import java.util.List;

import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.IZkStateListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.ZkConnection;
import org.apache.zookeeper.Watcher.Event.KeeperState;

public class ZkClientWatcher2 {

	/** zookeeper地址 */
	static final String CONNECT_ADDR = &quot;192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181&quot;;
	/** session超时时间 */
	static final int SESSION_OUTTIME = 5000;//ms 
	
	
	public static void main(String[] args) throws Exception {
		ZkClient zkc = new ZkClient(new ZkConnection(CONNECT_ADDR), 5000);
		
		zkc.createPersistent(&quot;/super&quot;, &quot;1234&quot;);
		
		//对父节点添加监听子节点变化。
		//------------------子节点变化----------------------
		zkc.subscribeChildChanges(&quot;/super&quot;, new IZkChildListener() {
			
			@Override
			public void handleChildChange(String arg0, List&amp;lt;String&amp;gt; arg1) throws Exception {
				// TODO Auto-generated method stub
				
			}
		});
//		//----------------节点状态变化--------------------------
//		zkc.subscribeStateChanges(new IZkStateListener() {
//			
//			@Override
//			public void handleStateChanged(KeeperState arg0) throws Exception {
//				// TODO Auto-generated method stub
//				
//			}
//			
//			@Override
//			public void handleNewSession() throws Exception {
//				// TODO Auto-generated method stub
//				
//			}
//		});
		//--------------节点数据变化----------------------------
		zkc.subscribeDataChanges(&quot;/super&quot;, new IZkDataListener() {
			@Override
			public void handleDataDeleted(String path) throws Exception {
				System.out.println(&quot;删除的节点为:&quot; + path);
			}
			
			@Override
			public void handleDataChange(String path, Object data) throws Exception {
				System.out.println(&quot;变更的节点为:&quot; + path + &quot;, 变更内容为:&quot; + data);
			}
			
		});
		Thread.sleep(3000);
		zkc.writeData(&quot;/super&quot;, &quot;456&quot;, -1);
		Thread.sleep(1000);

		zkc.delete(&quot;/super&quot;);
		Thread.sleep(Integer.MAX_VALUE);
		
		
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Qing Shuang</name></author><category term="Zookeeper" /><summary type="html">增删改查节点</summary></entry><entry><title type="html">Curator操作Zookeeper</title><link href="https://ronaldoshuang.github.io//2017/06/02/ZooKeeper-3/" rel="alternate" type="text/html" title="Curator操作Zookeeper" /><published>2017-06-02T00:00:00+00:00</published><updated>2017-06-02T00:00:00+00:00</updated><id>https://ronaldoshuang.github.io//2017/06/02/ZooKeeper-3</id><content type="html" xml:base="https://ronaldoshuang.github.io//2017/06/02/ZooKeeper-3/">&lt;h1 id=&quot;增删改查节点&quot;&gt;增删改查节点&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.curator.base;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooKeeper.States;
import org.apache.zookeeper.data.Stat;

public class CuratorBase {
	
	/** zookeeper地址 */
	static final String CONNECT_ADDR = &quot;192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181&quot;;
	/** session超时时间 */
	static final int SESSION_OUTTIME = 5000;//ms 
	
	public static void main(String[] args) throws Exception {
		
		//1 重试策略：初试时间为1s 重试10次
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
		//2 通过工厂创建连接
		CuratorFramework cf = CuratorFrameworkFactory.builder()
					.connectString(CONNECT_ADDR)
					.retryPolicy(retryPolicy)
					.sessionTimeoutMs(SESSION_OUTTIME)
					.connectionTimeoutMs(SESSION_OUTTIME)
//					.namespace(&quot;super&quot;)
					.build();
		//3 开启连接
		cf.start();
		
//		System.out.println(States.CONNECTED);
//		System.out.println(cf.getState());
		
		// 新加、删除
		/**
		//4 建立节点 指定节点类型（不加withMode默认为持久类型节点）、路径、数据内容
		cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(&quot;/super/c1&quot;,&quot;c1内容&quot;.getBytes());
		//5 删除节点
		cf.delete().guaranteed().deletingChildrenIfNeeded().forPath(&quot;/super&quot;);
		*/
		// 读取、修改
		/**
		//创建节点
//		cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(&quot;/super/c1&quot;,&quot;c1内容&quot;.getBytes());
//		cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(&quot;/super/c2&quot;,&quot;c2内容&quot;.getBytes());
		//读取节点
//		String ret1 = new String(cf.getData().forPath(&quot;/super/c2&quot;));
//		System.out.println(ret1);
	
		//修改节点
//		cf.setData().forPath(&quot;/super/c2&quot;, &quot;修改c2内容&quot;.getBytes());
//		String ret2 = new String(cf.getData().forPath(&quot;/super/c2&quot;));
//		System.out.println(ret2);	
		*/
		// 绑定回调函数
		/**
		ExecutorService pool = Executors.newCachedThreadPool();
		cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
		.inBackground(new BackgroundCallback() {
			@Override
			public void processResult(CuratorFramework cf, CuratorEvent ce) throws Exception {
				System.out.println(&quot;code:&quot; + ce.getResultCode());
				System.out.println(&quot;type:&quot; + ce.getType());
				System.out.println(&quot;线程为:&quot; + Thread.currentThread().getName());
			}
		}, pool)
		.forPath(&quot;/super/c3&quot;,&quot;c3内容&quot;.getBytes());
		Thread.sleep(Integer.MAX_VALUE);
		*/
		
		
		// 读取子节点getChildren方法 和 判断节点是否存在checkExists方法
		/**
		List&amp;lt;String&amp;gt; list = cf.getChildren().forPath(&quot;/super&quot;);
		for(String p : list){
			System.out.println(p);
		}
		
		Stat stat = cf.checkExists().forPath(&quot;/super/c3&quot;);
		System.out.println(stat);
		
		Thread.sleep(2000);
		cf.delete().guaranteed().deletingChildrenIfNeeded().forPath(&quot;/super&quot;);
		*/
		
		
		//cf.delete().guaranteed().deletingChildrenIfNeeded().forPath(&quot;/super&quot;);
		
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;watcher监听节点&quot;&gt;watcher监听节点&lt;/h1&gt;

&lt;h2 id=&quot;curatorwatcher1&quot;&gt;CuratorWatcher1&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.curator.watcher;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;

public class CuratorWatcher1 {
	
	/** zookeeper地址 */
	static final String CONNECT_ADDR = &quot;192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181&quot;;
	/** session超时时间 */
	static final int SESSION_OUTTIME = 5000;//ms 
	
	public static void main(String[] args) throws Exception {
		
		//1 重试策略：初试时间为1s 重试10次
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
		//2 通过工厂创建连接
		CuratorFramework cf = CuratorFrameworkFactory.builder()
					.connectString(CONNECT_ADDR)
					.sessionTimeoutMs(SESSION_OUTTIME)
					.retryPolicy(retryPolicy)
					.build();
		
		//3 建立连接
		cf.start();
		
		//4 建立一个cache缓存
		final NodeCache cache = new NodeCache(cf, &quot;/super&quot;, false);
		cache.start(true);
		cache.getListenable().addListener(new NodeCacheListener() {
			/**
			 * &amp;lt;B&amp;gt;方法名称：&amp;lt;/B&amp;gt;nodeChanged&amp;lt;BR&amp;gt;
			 * &amp;lt;B&amp;gt;概要说明：&amp;lt;/B&amp;gt;触发事件为创建节点和更新节点，在删除节点的时候并不触发此操作。&amp;lt;BR&amp;gt;
			 * @see org.apache.curator.framework.recipes.cache.NodeCacheListener#nodeChanged()
			 */
			@Override
			public void nodeChanged() throws Exception {
				System.out.println(&quot;路径为：&quot; + cache.getCurrentData().getPath());
				System.out.println(&quot;数据为：&quot; + new String(cache.getCurrentData().getData()));
				System.out.println(&quot;状态为：&quot; + cache.getCurrentData().getStat());
				System.out.println(&quot;---------------------------------------&quot;);
			}
		});
		
		Thread.sleep(1000);
		cf.create().forPath(&quot;/super&quot;, &quot;123&quot;.getBytes());
		
		Thread.sleep(1000);
		cf.setData().forPath(&quot;/super&quot;, &quot;456&quot;.getBytes());
		
		Thread.sleep(1000);
		cf.delete().forPath(&quot;/super&quot;);
		
		Thread.sleep(Integer.MAX_VALUE);
		
		

	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;curatorwatcher2&quot;&gt;CuratorWatcher2&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.curator.watcher;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;

public class CuratorWatcher2 {
	
	/** zookeeper地址 */
	static final String CONNECT_ADDR = &quot;192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181&quot;;
	/** session超时时间 */
	static final int SESSION_OUTTIME = 5000;//ms 
	
	public static void main(String[] args) throws Exception {
		
		//1 重试策略：初试时间为1s 重试10次
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
		//2 通过工厂创建连接
		CuratorFramework cf = CuratorFrameworkFactory.builder()
					.connectString(CONNECT_ADDR)
					.sessionTimeoutMs(SESSION_OUTTIME)
					.retryPolicy(retryPolicy)
					.build();
		
		//3 建立连接
		cf.start();
		
		//4 建立一个PathChildrenCache缓存,第三个参数为是否接受节点数据内容 如果为false则不接受
		PathChildrenCache cache = new PathChildrenCache(cf, &quot;/super&quot;, true);
		//5 在初始化的时候就进行缓存监听
		cache.start(StartMode.POST_INITIALIZED_EVENT);
		cache.getListenable().addListener(new PathChildrenCacheListener() {
			/**
			 * &amp;lt;B&amp;gt;方法名称：&amp;lt;/B&amp;gt;监听子节点变更&amp;lt;BR&amp;gt;
			 * &amp;lt;B&amp;gt;概要说明：&amp;lt;/B&amp;gt;新建、修改、删除&amp;lt;BR&amp;gt;
			 * @see org.apache.curator.framework.recipes.cache.PathChildrenCacheListener#childEvent(org.apache.curator.framework.CuratorFramework, org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent)
			 */
			@Override
			public void childEvent(CuratorFramework cf, PathChildrenCacheEvent event) throws Exception {
				switch (event.getType()) {
				case CHILD_ADDED:
					System.out.println(&quot;CHILD_ADDED :&quot; + event.getData().getPath());
					break;
				case CHILD_UPDATED:
					System.out.println(&quot;CHILD_UPDATED :&quot; + event.getData().getPath());
					break;
				case CHILD_REMOVED:
					System.out.println(&quot;CHILD_REMOVED :&quot; + event.getData().getPath());
					break;
				default:
					break;
				}
			}
		});

		//创建本身节点不发生变化
		cf.create().forPath(&quot;/super&quot;, &quot;init&quot;.getBytes());
		
		//添加子节点
		Thread.sleep(1000);
		cf.create().forPath(&quot;/super/c1&quot;, &quot;c1内容&quot;.getBytes());
		Thread.sleep(1000);
		cf.create().forPath(&quot;/super/c2&quot;, &quot;c2内容&quot;.getBytes());
		
		//修改子节点
		Thread.sleep(1000);
		cf.setData().forPath(&quot;/super/c1&quot;, &quot;c1更新内容&quot;.getBytes());
		
		//删除子节点
		Thread.sleep(1000);
		cf.delete().forPath(&quot;/super/c2&quot;);		
		
		//删除本身节点
		Thread.sleep(1000);
		cf.delete().deletingChildrenIfNeeded().forPath(&quot;/super&quot;);
		
		Thread.sleep(Integer.MAX_VALUE);
		

	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;集群下的操作zookeeper&quot;&gt;集群下的操作zookeeper&lt;/h1&gt;

&lt;p&gt;CuratorWatcher.java类&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.curator.cluster;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class CuratorWatcher {

	/** 父节点path */
	static final String PARENT_PATH = &quot;/super&quot;;
	
	/** zookeeper服务器地址 */
	public static final String CONNECT_ADDR = &quot;192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181&quot;;	/** 定义session失效时间 */
	
	public static final int SESSION_TIMEOUT = 30000;
	
	public CuratorWatcher() throws Exception{
		//1 重试策略：初试时间为1s 重试10次
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
		//2 通过工厂创建连接
		CuratorFramework cf = CuratorFrameworkFactory.builder()
					.connectString(CONNECT_ADDR)
					.sessionTimeoutMs(SESSION_TIMEOUT)
					.retryPolicy(retryPolicy)
					.build();
		//3 建立连接
		cf.start();
		
		//4 创建跟节点
		if(cf.checkExists().forPath(PARENT_PATH) == null){
			cf.create().withMode(CreateMode.PERSISTENT).forPath(PARENT_PATH,&quot;super init&quot;.getBytes());
		}

		//4 建立一个PathChildrenCache缓存,第三个参数为是否接受节点数据内容 如果为false则不接受
		PathChildrenCache cache = new PathChildrenCache(cf, PARENT_PATH, true);
		//5 在初始化的时候就进行缓存监听
		cache.start(StartMode.POST_INITIALIZED_EVENT);
		cache.getListenable().addListener(new PathChildrenCacheListener() {
			/**
			 * &amp;lt;B&amp;gt;方法名称：&amp;lt;/B&amp;gt;监听子节点变更&amp;lt;BR&amp;gt;
			 * &amp;lt;B&amp;gt;概要说明：&amp;lt;/B&amp;gt;新建、修改、删除&amp;lt;BR&amp;gt;
			 * @see org.apache.curator.framework.recipes.cache.PathChildrenCacheListener#childEvent(org.apache.curator.framework.CuratorFramework, org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent)
			 */
			@Override
			public void childEvent(CuratorFramework cf, PathChildrenCacheEvent event) throws Exception {
				switch (event.getType()) {
				case CHILD_ADDED:
					System.out.println(&quot;CHILD_ADDED :&quot; + event.getData().getPath());
					System.out.println(&quot;CHILD_ADDED :&quot; + new String(event.getData().getData()));
					break;
				case CHILD_UPDATED:
					System.out.println(&quot;CHILD_UPDATED :&quot; + event.getData().getPath());
					System.out.println(&quot;CHILD_UPDATED :&quot; + new String(event.getData().getData()));
					break;
				case CHILD_REMOVED:
					System.out.println(&quot;CHILD_REMOVED :&quot; + event.getData().getPath());
					System.out.println(&quot;CHILD_REMOVED :&quot; + new String(event.getData().getData()));
					break;
				default:
					break;
				}
			}
		});
	}

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Client1.java类&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.curator.cluster;

public class Client1 {

	public static void main(String[] args) throws Exception{
		
		CuratorWatcher watcher = new CuratorWatcher();
		Thread.sleep(100000000);
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Client2.java类&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.curator.cluster;
 
public class Client1 {

	public static void main(String[] args) throws Exception{
		
		CuratorWatcher watcher = new CuratorWatcher();
		Thread.sleep(100000000);
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Test.java类&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.curator.cluster;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;

public class Test {

	/** zookeeper地址 */
	static final String CONNECT_ADDR = &quot;192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181&quot;;
	/** session超时时间 */
	static final int SESSION_OUTTIME = 5000;//ms 
	
	public static void main(String[] args) throws Exception {
		
		//1 重试策略：初试时间为1s 重试10次
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
		//2 通过工厂创建连接
		CuratorFramework cf = CuratorFrameworkFactory.builder()
					.connectString(CONNECT_ADDR)
					.sessionTimeoutMs(SESSION_OUTTIME)
					.retryPolicy(retryPolicy)
					.build();
		//3 开启连接
		cf.start();

		
		Thread.sleep(3000);
		System.out.println(cf.getChildren().forPath(&quot;/super&quot;).get(0));
		
		//4 创建节点
//		Thread.sleep(1000);
		cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(&quot;/super/c1&quot;,&quot;c1内容&quot;.getBytes());
		Thread.sleep(1000);
//		cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(&quot;/super/c2&quot;,&quot;c2内容&quot;.getBytes());
//		Thread.sleep(1000);
//		
//		
//		
//		//5 读取节点
//		Thread.sleep(1000);
//		String ret1 = new String(cf.getData().forPath(&quot;/super/c1&quot;));
//		System.out.println(ret1);
//
//		
//		//6 修改节点
		Thread.sleep(1000);
		cf.setData().forPath(&quot;/super/c2&quot;, &quot;修改的新c2内容&quot;.getBytes());
		String ret2 = new String(cf.getData().forPath(&quot;/super/c2&quot;));
		System.out.println(ret2);	
//		
//
//		
//		//7 删除节点
//		Thread.sleep(1000);
//		cf.delete().forPath(&quot;/super/c1&quot;);
  
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;curator提供的分布式锁&quot;&gt;Curator提供的分布式锁&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.curator.lock;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;


public class Lock2 {

	/** zookeeper地址 */
	static final String CONNECT_ADDR = &quot;192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181&quot;;
	/** session超时时间 */
	static final int SESSION_OUTTIME = 5000;//ms 
	
	static int count = 10;
	public static void genarNo(){
		try {
			count--;
			System.out.println(count);
		} finally {
		
		}
	}
	
	public static void main(String[] args) throws Exception {
		
		//1 重试策略：初试时间为1s 重试10次
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
		//2 通过工厂创建连接
		CuratorFramework cf = CuratorFrameworkFactory.builder()
					.connectString(CONNECT_ADDR)
					.sessionTimeoutMs(SESSION_OUTTIME)
					.retryPolicy(retryPolicy)
//					.namespace(&quot;super&quot;)
					.build();
		//3 开启连接
		cf.start();
		
		//4 分布式锁
		final InterProcessMutex lock = new InterProcessMutex(cf, &quot;/super&quot;);
		//final ReentrantLock reentrantLock = new ReentrantLock();
		final CountDownLatch countdown = new CountDownLatch(1);
		
		for(int i = 0; i &amp;lt; 10; i++){
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						countdown.await();
						//加锁
						lock.acquire();
						//reentrantLock.lock();
						//-------------业务处理开始
						//genarNo();
						SimpleDateFormat sdf = new SimpleDateFormat(&quot;HH:mm:ss|SSS&quot;);
						System.out.println(sdf.format(new Date()));
						//System.out.println(System.currentTimeMillis());
						//-------------业务处理结束
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						try {
							//释放
							lock.release();
							//reentrantLock.unlock();
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			},&quot;t&quot; + i).start();
		}
		Thread.sleep(100);
		countdown.countDown();	 
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;curator提供的栅栏barrier&quot;&gt;Curator提供的栅栏Barrier&lt;/h1&gt;

&lt;p&gt;CuratorBarrier1.java&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.curator.barrier;

import java.util.Random;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.barriers.DistributedDoubleBarrier;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class CuratorBarrier1 {

	/** zookeeper地址 */
	static final String CONNECT_ADDR = &quot;192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181&quot;;
	/** session超时时间 */
	static final int SESSION_OUTTIME = 5000;//ms 
	
	public static void main(String[] args) throws Exception {
		
		
		
		for(int i = 0; i &amp;lt; 5; i++){
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
						CuratorFramework cf = CuratorFrameworkFactory.builder()
									.connectString(CONNECT_ADDR)
									.retryPolicy(retryPolicy)
									.build();
						cf.start();
						DistributedDoubleBarrier barrier = new DistributedDoubleBarrier(cf, &quot;/super&quot;, 5);
						Thread.sleep(1000 * (new Random()).nextInt(3)); 
						System.out.println(Thread.currentThread().getName() + &quot;已经准备&quot;);
						barrier.enter();
						System.out.println(&quot;同时开始运行...&quot;);
						Thread.sleep(1000 * (new Random()).nextInt(3));
						System.out.println(Thread.currentThread().getName() + &quot;运行完毕&quot;);
						barrier.leave();
						System.out.println(&quot;同时退出运行...&quot;);
						

					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			},&quot;t&quot; + i).start();
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;CuratorBarrier2.java&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.curator.barrier;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.atomic.AtomicValue;
import org.apache.curator.framework.recipes.atomic.DistributedAtomicInteger;
import org.apache.curator.framework.recipes.barriers.DistributedBarrier;
import org.apache.curator.framework.recipes.barriers.DistributedDoubleBarrier;
import org.apache.curator.framework.recipes.queue.DistributedQueue;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.retry.RetryNTimes;

public class CuratorBarrier2 {

	/** zookeeper地址 */
	static final String CONNECT_ADDR = &quot;192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181&quot;;
	/** session超时时间 */
	static final int SESSION_OUTTIME = 5000;//ms 
	
	static DistributedBarrier barrier = null;
	
	public static void main(String[] args) throws Exception {
		
		
		
		for(int i = 0; i &amp;lt; 5; i++){
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
						CuratorFramework cf = CuratorFrameworkFactory.builder()
									.connectString(CONNECT_ADDR)
									.sessionTimeoutMs(SESSION_OUTTIME)
									.retryPolicy(retryPolicy)
									.build();
						cf.start();
						barrier = new DistributedBarrier(cf, &quot;/super&quot;);
						System.out.println(Thread.currentThread().getName() + &quot;设置barrier!&quot;);
						barrier.setBarrier();	//设置
						barrier.waitOnBarrier();	//等待
						System.out.println(&quot;---------开始执行程序----------&quot;);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			},&quot;t&quot; + i).start();
		}

		Thread.sleep(5000);
		barrier.removeBarrier();	//释放
		
		
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;curator提供的atomicinteger&quot;&gt;Curator提供的AtomicInteger&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.curator.atomicinteger;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.atomic.AtomicValue;
import org.apache.curator.framework.recipes.atomic.DistributedAtomicInteger;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.retry.RetryNTimes;

public class CuratorAtomicInteger {

	/** zookeeper地址 */
	static final String CONNECT_ADDR = &quot;192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181&quot;;
	/** session超时时间 */
	static final int SESSION_OUTTIME = 5000;//ms 
	
	public static void main(String[] args) throws Exception {
		
		//1 重试策略：初试时间为1s 重试10次
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
		//2 通过工厂创建连接
		CuratorFramework cf = CuratorFrameworkFactory.builder()
					.connectString(CONNECT_ADDR)
					.sessionTimeoutMs(SESSION_OUTTIME)
					.retryPolicy(retryPolicy)
					.build();
		//3 开启连接
		cf.start();
		//cf.delete().forPath(&quot;/super&quot;);
		

		//4 使用DistributedAtomicInteger
		DistributedAtomicInteger atomicIntger = 
				new DistributedAtomicInteger(cf, &quot;/super&quot;, new RetryNTimes(3, 1000));
		
		AtomicValue&amp;lt;Integer&amp;gt; value = atomicIntger.add(1);
		System.out.println(value.succeeded());
		System.out.println(value.postValue());	//最新值
		System.out.println(value.preValue());	//原始值
		
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Qing Shuang</name></author><category term="Zookeeper" /><summary type="html">增删改查节点</summary></entry><entry><title type="html">Zookeeper视频学习总结</title><link href="https://ronaldoshuang.github.io//2017/06/01/ZooKeeper-0/" rel="alternate" type="text/html" title="Zookeeper视频学习总结" /><published>2017-06-01T00:00:00+00:00</published><updated>2017-06-01T00:00:00+00:00</updated><id>https://ronaldoshuang.github.io//2017/06/01/ZooKeeper-0</id><content type="html" xml:base="https://ronaldoshuang.github.io//2017/06/01/ZooKeeper-0/">&lt;h1 id=&quot;zookeeper简介&quot;&gt;Zookeeper简介&lt;/h1&gt;

&lt;p&gt;Zookeeper是一个高效的分布式协调服务，他暴露一些公用服务，比如命名/配置/同步控制/群组服务等。我们可以使用zk来实现比如达成共识/集群管理/leader选举等。&lt;/p&gt;

&lt;p&gt;zookeeper是一个高可用的分布式管理与协调框架，基于ZAB算法（原子消息广播协议）的实现，该框架能够很好的保证分布式环境中数据的一致性。也正是基于这样的特点，使得zk成为了解决分布式一致性问题的利器。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;顺序一致性：从一个客户发起的事务请求，最终会严格地按照其发起的顺序被应用的zk中。

原子性：所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的，也就是说要么整个集群所有的机器都成功应用了某个事务，要么有没有应用，一定不会出现部分机器应用了该事务，而另一部分没有应用的情况。

单一视图：无论客户端连接的是哪个zk服务器，其看到的服务器端数据模型都是一致的。

可靠性：一旦服务器成功应用了一个事务，并完成对客户端的响应，那么该事务所引起的服务器端状态将会被一致保留下来，除非有另一个事务对其更改

实时性：通常所说的实时性就是指一旦事务被成功应用，那么客户端就能立刻从服务器获取变更后的数据，zk仅仅能保证在一段时间内，客户端最终一定能从服务器端读取最新的数据状态
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;zookeeper设计目标&quot;&gt;Zookeeper设计目标&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;目标一：简单的数据结构，zk就是以简单的树形结构来进行互相协调的（也叫树形名字空间）

目标二：可以构建集群。一般zk集群通常由一组机器构成，一般3-5台机器就可以组成一个Zookeeper集群了，只要集群中超过半数以上的机会能够正常工作，那么整个集群就能够正常对外提供服务。

目标三：顺序访问。对于来自每个客户端的每个请求，Zookeeper都会分配一个全局唯一的递增编号，这个标号反应了所有事务操作的先后顺序。应用程序可以使用Zookeeper的这个特性来实现更高层次的同步。

目标四：高性能。由于Zookeeper将全量数据存储在内存中，并直接服务于所有的非事务请求，因此尤其是在读操作为主的场景下性能非常突出。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;zookeeper的结构&quot;&gt;Zookeeper的结构&lt;/h1&gt;

&lt;p&gt;Zookeeper会维护一个具有层次关系的数据结构，它非常类似于一个标准的文件系统。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ronaldoshuang.github.io//images/posts/Zookeeper/Zookeeper-0-1.png&quot; alt=&quot;Zookeeper结构.png&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;zookeeper的数据模型&quot;&gt;Zookeeper的数据模型&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. 每个子目录项如NameService都被称为znode，这个znode是被他所在的路径唯一标识，如Server1这个znode的标识为/NameService/Server1
2. znode可以有子节点目录，并且每个znode可以存储数据，注意EPHEMERAL类型（临时）的目录节点不能有子节点目录
3. znode是有版本的，每个znode中可以存储的数据可以有多个版本，也就是一个访问路径可以储存多份数据
4. znode可以是临时节点，一旦创建这个znode的客户端与服务器失去联系，这个znode也将自动删除，Zookeeper的客户端和服务器通信采用长连接方式，每个客户端和服务器通过心跳来保持连接，这个连接状态称为session，如果znode是临时节点，这个session失效，znode也就删除了。
5. znode的目录名可以自动编号，如App1已经存在，在创建的话，将会自动命名为App2
6. znode可以被监控，包括这个目录节点中存储的数据的修改，子节点目录的变化等。一旦变化可以通知设置监控的客户端，这个是Zookeeper的核心特性。Zookeeper的很多功能都是基于这个特性实现的，后面在典型应用场景中会有实例介绍。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;zookeeper组成&quot;&gt;Zookeeper组成&lt;/h1&gt;

&lt;p&gt;ZK server根据其身份特性分为三种：leader，follower，observer，其中follower和observer又统称为learner（学习者）&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;leader：负责客户端的写请求
follower：负责客户端的读请求，参与leader选举等
observer：特殊的&quot;follower&quot;，其可以接受客户端读的请求，但不参与选举。（扩容系统支撑能力，提高了读速度。因为他不接受任何同步地写入请求，只负责与leader同步数据）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;zookeeper应用场景&quot;&gt;Zookeeper应用场景&lt;/h1&gt;

&lt;p&gt;Zookeeper从设计模式角度来看，是一个基于观察者模式设计的分布式服务管理框架，他负责存储和管理大家都关心的数据，然后接受观察者的注册，一旦这些数据的状态发生变化，Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应，从而实现集群中类似master/slave管理模式。&lt;/p&gt;

&lt;h2 id=&quot;配置管理&quot;&gt;配置管理&lt;/h2&gt;

&lt;p&gt;配置的管理在分布式应用环境中很常见，比如我们在平常的应用系统中，经常会碰到这样的需求：如机器的配置列表，运行时开关配置，数据库配置信息，这些全局配置信息通常具备以下3个特性：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. 数据量比较小
2. 数据内容在运行时动态发送变化
3. 集群中各个节点共享信息，配置一致
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;集群管理&quot;&gt;集群管理&lt;/h2&gt;

&lt;p&gt;Zookeeper不仅能够帮你维护当前的集群中机器的服务状态，而且能够帮你选出一个leader，让这个leader来管理集群这就是Zookeeper的另一个功能&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. 希望知道当前集群中究竟有多少机器工作
2. 对集群中每天集群的运行时状态进行数据收集
3. 对集群中每台集群进行上下线操作
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;发布与订阅&quot;&gt;发布与订阅&lt;/h2&gt;

&lt;p&gt;Zookeeper是一个典型的发布订阅模式的分布式数控管理与协调框架，开发人员可以使用它来进行分布式数据的发布与订阅。&lt;/p&gt;

&lt;h2 id=&quot;数据库切换&quot;&gt;数据库切换&lt;/h2&gt;

&lt;p&gt;比如我们初始化Zookeeper的时候读取其节点上数据库配置文件，当配置一旦发生变更时，Zookeeper就能帮助我们把变更的通知发生到各个客户端，每个客户端在接收到这个变更通知后，就可以重新进行最新消息数据获取。&lt;/p&gt;

&lt;h2 id=&quot;分布式日志的收集&quot;&gt;分布式日志的收集&lt;/h2&gt;

&lt;p&gt;做一个日志收集集群中所有的日志信息，进行统一管理&lt;/p&gt;

&lt;h2 id=&quot;分布式锁队列管理&quot;&gt;分布式锁，队列管理&lt;/h2&gt;

&lt;p&gt;Zookeeper的特性就是在分布式场景下高可用。&lt;/p&gt;

&lt;h1 id=&quot;zookeeper集群搭建&quot;&gt;Zookeeper集群搭建&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1.1 结构：一共三个节点

(zk服务器集群规模不小于3个节点),要求服务器之间系统时间保持一致。

1.2 上传zk 

进行解压： tar zookeeper-3.4.5.tar.gz

重命名： mv zookeeper-3.4.5 zookeeper

修改环境变量： vi /etc/profile
   export ZOOKEEPER_HOME=/usr/local/zookeeper
   export PATH=.:$HADOOP_HOME/bin:$ZOOKEEPER_HOME/bin:$JAVA_HOME/...

刷新： source /etc/profile

到zookeeper下修改配置文件
   cd /usr/local/zookeeper/conf
   mv zoo_sample.cfg zoo.cfg 

修改conf: vi zoo.cfg 修改两处
  （1）dataDir=/usr/local/zookeeper/data
  （2）最后面添加
	server.0=bhz:2888:3888
	server.1=hadoop1:2888:3888
	server.2=hadoop2:2888:3888

服务器标识配置：
   创建文件夹：mkdir data
   创建文件myid并填写内容为0：vi myid (内容为服务器标识 ： 0)

进行复制zookeeper目录到hadoop01和hadoop02还有/etc/profile文件

把hadoop01、hadoop02中的myid文件里的值修改为1和2路径(vi /usr/local/zookeeper/data/myid)

启动zookeeper：路径：/usr/local/zookeeper/bin
   执行：zkServer.sh start(注意这里3台机器都要进行启动)
   状态：zkServer.sh status(在三个节点上检验zk的mode,一个leader和俩个follower)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;zookeeper操作shell&quot;&gt;Zookeeper操作shell&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zkCli.sh 进入zookeeper客户端
根据提示命令进行操作：
查找：ls / ls /zookeeper
创建并赋值：create /bhz hadoop
获取：get /bhz
设值：set /bhz baihezhuo
可以看到zookeeper集群的数据一致性
创建节点有俩种类型：短暂（ephemeral）
持久（persistent）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;配置文件zoocfg详解&quot;&gt;配置文件zoo.cfg详解&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;tickTime： 基本事件单元，以毫秒为单位。这个时间是作为 Zookeeper
           服务器之间或客户端与服务器之间维持心跳的时间间隔，
           也就是每隔 tickTime时间就会发送一个心跳。

 dataDir：存储内存中数据库快照的位置，顾名思义就是 Zookeeper
          保存数据的目录，默认情况下，Zookeeper
          将写数据的日志文件也保存在这个目录里。

 clientPort： 这个端口就是客户端连接 Zookeeper 服务器的端口，
             Zookeeper会监听这个端口，接受客户端的访问请求。

 initLimit： 这个配置项是用来配置 Zookeeper
             接受客户端初始化连接时最长能忍受多少个心跳时间间隔数，
             当已经超过 10 个心跳的时间（也就是 tickTime）长度后
             Zookeeper 服务器还没有收到客户端的返回信息，
             那么表明这个客户端连接失败。总的时间长度就是
             10*2000=20 秒。
 
 syncLimit： 这个配置项标识 Leader 与 Follower
		     之间发送消息，请求和应答时间长度，
		     最长不能超过多少个 tickTime
		    的时间长度，总的时间长度就是 5*2000=10 秒

 server.A = B:C:D :
             A表示这个是第几号服务器,
		     B 是这个服务器的 ip 地址；
		     C 表示的是这个服务器与集群中的 Leader
		    服务器交换信息的端口；
		     D 表示的是万一集群中的 Leader
		    服务器挂了，需要一个端口来重新进行选举，选出一个新的 Leader
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;java操作zookeeper&quot;&gt;java操作zookeeper&lt;/h1&gt;

&lt;p&gt;查看示例代码&lt;/p&gt;

&lt;h1 id=&quot;watcherzk状态事件类型&quot;&gt;Watcher，zk状态，事件类型&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zookeeper有watch事件，是一次性触发的，当watch监视的数据发生变化时。通知设置了该watch的client，即watcher.
  同样，其watcher 是监听数据发送了某些变化，那就一定会有对应的事件类型和状态类型。

  事件类型:  (znode节点相关的)
  EventType.NodeCreated
  EventType.NodeDataChanged
  EventType.NodeChildrenChanged
  EventType.NodeDeleted

  状态类型: (是跟客户端实例相关的)
  KeeperState.Disconnected
  KeeperState.SyncConnected
  KeeperState.AuthFailed
  KeeperState.Expired
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;watcher的特性:一次性、客户端串行执行、轻量。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  一次性:对于ZK的watcher, 你只需要记住一点: zookeeper有watch事件，是一次性触发的，当watch监视的数据发生变化时，通知设置了该watch的client,  即watcher,由于zookeeper 的监控都是一次性的所以每次必须设置监控。

  客户端串行执行:客户端Watcher回调的过程是一个串行同步的过程，这为我们保证了顺序，同时需要开发人员注章一点，千万不要因为一一个Watcher的处理逻辑影响了整个客户端的Watcher回调。

  轻量: WatchedEvent是Zookeeper整 个Watcher通知机制的最小通知单元，整个暑假结构只包含三部分:通知状态、事件类型和节点路径。也就是说Watcher通知非常的简单，只会告诉客户端发生了事件而不会告知其具体内容，需要客户自己去进行获取，比如NodeDataChanged事件， Zookeeper只会通知客户端指定节点的数据发生了变更，  而不会直接提供具体的数据内容。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;zookeeper的aclauth&quot;&gt;zookeeper的ACL(AUTH)&lt;/h1&gt;

&lt;p&gt;ACL(Access Control List)Zookeeper作为一个分布式协调框架，其内部存储的都是一些关乎分布式系统运行时状态的元数据，尤其是设计到一些分布式锁、Master选举和协调等应用场景。我们需要有效地保障Zookee per中的数据安全，Zookeeper提供一套完善的ACL权限控制机制来保障数据的安全.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ZK提供了三种模式。权限模式、投权对象、权限。

权限模式: Scheme,  开发人员最多使用的如下四种权限模式:
	  IP: ip模式通过ip地址粒度来进行控制权限，例如配置了: ip:192.168.1.107即表示权限控制都是针对这个ip地址的，同时也支持按网段分配。比如1192.168.1.
	
	  Digest: digest是最常用的权限控制模式，也更符合我们对权限控制的认识，其类似于&quot;usemame; password形式的权限标设进行权限配置。ZK会对形成的权限标识先后进行俩次编码处理，分别是SHA-1加密算法、BASE64编码。
	
	  World: World是一直最开放的权限控制模式。这种模式可以看做为特殊的Digest,他仅仅是一个标识而已。
	
	  Super:超级用户模式，在超级用户模式下可以对ZK在意进行操作。

权限对象：指的是权限赋予的用户或者一个指定的实体，例如ip地址或机器等。在不同的模式下，授权对象是不同的，这种模式和权限对象一一对应

权限：权限就是指那些通过权限检测后可以被允许执行的操作，在zk中，对数据的操作权限分为以下五大类：
    CREATE、DELETE、READ、WRITE、ADMIN
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;zkclient使用&quot;&gt;zkClient使用&lt;/h1&gt;

&lt;p&gt;查看示例代码&lt;/p&gt;

&lt;h1 id=&quot;curator框架&quot;&gt;Curator框架&lt;/h1&gt;

&lt;p&gt;session超时重连，主从选举，分布式计数器，分布式锁等适用于各种复杂的zk场景api封装。&lt;/p&gt;

&lt;p&gt;查看示例代码&lt;/p&gt;</content><author><name>Qing Shuang</name></author><category term="Zookeeper" /><summary type="html">Zookeeper简介</summary></entry><entry><title type="html">java操作Zookeeper</title><link href="https://ronaldoshuang.github.io//2017/06/01/ZooKeeper-1/" rel="alternate" type="text/html" title="java操作Zookeeper" /><published>2017-06-01T00:00:00+00:00</published><updated>2017-06-01T00:00:00+00:00</updated><id>https://ronaldoshuang.github.io//2017/06/01/ZooKeeper-1</id><content type="html" xml:base="https://ronaldoshuang.github.io//2017/06/01/ZooKeeper-1/">&lt;h1 id=&quot;增删改查节点&quot;&gt;增删改查节点&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.zookeeper.base;

import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
   
public class ZookeeperBase {

	/** zookeeper地址 */
	static final String CONNECT_ADDR = &quot;192.168.80.88:2181,192.168.80.87:2181,192.168.80.86:2181&quot;;
	/** session超时时间 */
	static final int SESSION_OUTTIME = 2000;//ms 
	/** 信号量，阻塞程序执行，用于等待zookeeper连接成功，发送成功信号 */
	static final CountDownLatch connectedSemaphore = new CountDownLatch(1);
	
	public static void main(String[] args) throws Exception{
		
		ZooKeeper zk = new ZooKeeper(CONNECT_ADDR, SESSION_OUTTIME, new Watcher(){
			@Override
			public void process(WatchedEvent event) {
				//获取事件的状态
				KeeperState keeperState = event.getState();
				EventType eventType = event.getType();
				//如果是建立连接
				//KeeperState.ConnectedReadOnly
				//``KeeperState.Disconnected  
				//``KeeperState.Expired
				//KeeperState.NoSyncConnected
				//``KeeperState.SyncConnected
				//KeeperState.Unknown
				if(KeeperState.SyncConnected == keeperState){
					if(EventType.None == eventType){
						//如果建立连接成功，则发送信号量，让后续阻塞程序向下执行
						connectedSemaphore.countDown();
						System.out.println(&quot;zk 建立连接&quot;);
					}
				}
			}
		});

		//进行阻塞
		connectedSemaphore.await();
		
		System.out.println(&quot;..&quot;);
		//创建父节点
		zk.create(&quot;/testRoot&quot;, &quot;testRoot&quot;.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT,);
		
		//创建子节点
		zk.create(&quot;/testRoot/children&quot;, &quot;children data&quot;.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
		
		//获取节点洗信息
		
		byte[] data = zk.getData(&quot;/testRoot&quot;, false, null);
		System.out.println(new String(data));
		System.out.println(zk.getChildren(&quot;/testRoot&quot;, false));
		
		//修改节点的值
		zk.setData(&quot;/testRoot&quot;, &quot;modify data root&quot;.getBytes(), -1);
		byte[] data = zk.getData(&quot;/testRoot&quot;, false, null);
		System.out.println(new String(data));		
		
		//判断节点是否存在
		System.out.println(zk.exists(&quot;/testRoot/children&quot;, false));
		//删除节点
		zk.delete(&quot;/testRoot/children&quot;, -1);
		System.out.println(zk.exists(&quot;/testRoot/children&quot;, false));
		
		zk.close();	
	}	
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;watcher监听节点&quot;&gt;watcher监听节点&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.zookeeper.watcher;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

/**
 * Zookeeper Wathcher 
 * 本类就是一个Watcher类（实现了org.apache.zookeeper.Watcher类）
 */
public class ZooKeeperWatcher implements Watcher {

	/** 定义原子变量 */
	AtomicInteger seq = new AtomicInteger();
	/** 定义session失效时间 */
	private static final int SESSION_TIMEOUT = 10000;
	/** zookeeper服务器地址 */
	private static final String CONNECTION_ADDR = &quot;192.168.80.88:2181&quot;;
	/** zk父路径设置 */
	private static final String PARENT_PATH = &quot;/testWatch&quot;;
	/** zk子路径设置 */
	private static final String CHILDREN_PATH = &quot;/testWatch/children&quot;;
	/** 进入标识 */
	private static final String LOG_PREFIX_OF_MAIN = &quot;【Main】&quot;;
	/** zk变量 */
	private ZooKeeper zk = null;
	/** 信号量设置，用于等待zookeeper连接建立之后 通知阻塞程序继续向下执行 */
	private CountDownLatch connectedSemaphore = new CountDownLatch(1);

	/**
	 * 创建ZK连接
	 * @param connectAddr ZK服务器地址列表
	 * @param sessionTimeout Session超时时间
	 */
	public void createConnection(String connectAddr, int sessionTimeout) {
		this.releaseConnection();
		try {
			zk = new ZooKeeper(connectAddr, sessionTimeout, this);
			System.out.println(LOG_PREFIX_OF_MAIN + &quot;开始连接ZK服务器&quot;);
			connectedSemaphore.await();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 关闭ZK连接
	 */
	public void releaseConnection() {
		if (this.zk != null) {
			try {
				this.zk.close();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 创建节点
	 * @param path 节点路径
	 * @param data 数据内容
	 * @return 
	 */
	public boolean createPath(String path, String data) {
		try {
			//设置监控(由于zookeeper的监控都是一次性的所以 每次必须设置监控)
			this.zk.exists(path, true);
			System.out.println(LOG_PREFIX_OF_MAIN + &quot;节点创建成功, Path: &quot; + 
							   this.zk.create(	/**路径*/ 
									   			path, 
									   			/**数据*/
									   			data.getBytes(), 
									   			/**所有可见*/
								   				Ids.OPEN_ACL_UNSAFE, 
								   				/**永久存储*/
								   				CreateMode.PERSISTENT ) + 	
							   &quot;, content: &quot; + data);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * 读取指定节点数据内容
	 * @param path 节点路径
	 * @return
	 */
	public String readData(String path, boolean needWatch) {
		try {
			return new String(this.zk.getData(path, needWatch, null));
		} catch (Exception e) {
			e.printStackTrace();
			return &quot;&quot;;
		}
	}

	/**
	 * 更新指定节点数据内容
	 * @param path 节点路径
	 * @param data 数据内容
	 * @return
	 */
	public boolean writeData(String path, String data) {
		try {
			System.out.println(LOG_PREFIX_OF_MAIN + &quot;更新数据成功，path：&quot; + path + &quot;, stat: &quot; +
								this.zk.setData(path, data.getBytes(), -1));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * 删除指定节点
	 * 
	 * @param path
	 *节点path
	 */
	public void deleteNode(String path) {
		try {
			this.zk.delete(path, -1);
			System.out.println(LOG_PREFIX_OF_MAIN + &quot;删除节点成功，path：&quot; + path);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 判断指定节点是否存在
	 * @param path 节点路径
	 */
	public Stat exists(String path, boolean needWatch) {
		try {
			return this.zk.exists(path, needWatch);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 获取子节点
	 * @param path 节点路径
	 */
	private List&amp;lt;String&amp;gt; getChildren(String path, boolean needWatch) {
		try {
			return this.zk.getChildren(path, needWatch);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 删除所有节点
	 */
	public void deleteAllTestPath() {
		if(this.exists(CHILDREN_PATH, false) != null){
			this.deleteNode(CHILDREN_PATH);
		}
		if(this.exists(PARENT_PATH, false) != null){
			this.deleteNode(PARENT_PATH);
		}		
	}
	
	/**
	 * 收到来自Server的Watcher通知后的处理。
	 */
	@Override
	public void process(WatchedEvent event) {
		
		System.out.println(&quot;进入 process 。。。。。event = &quot; + event);
		
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		if (event == null) {
			return;
		}
		
		// 连接状态
		KeeperState keeperState = event.getState();
		// 事件类型
		EventType eventType = event.getType();
		// 受影响的path
		String path = event.getPath();
		
		String logPrefix = &quot;【Watcher-&quot; + this.seq.incrementAndGet() + &quot;】&quot;;

		System.out.println(logPrefix + &quot;收到Watcher通知&quot;);
		System.out.println(logPrefix + &quot;连接状态:\t&quot; + keeperState.toString());
		System.out.println(logPrefix + &quot;事件类型:\t&quot; + eventType.toString());

		if (KeeperState.SyncConnected == keeperState) {
			// 成功连接上ZK服务器
			if (EventType.None == eventType) {
				System.out.println(logPrefix + &quot;成功连接上ZK服务器&quot;);
				connectedSemaphore.countDown();
			} 
			//创建节点
			else if (EventType.NodeCreated == eventType) {
				System.out.println(logPrefix + &quot;节点创建&quot;);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				this.exists(path, true);
			} 
			//更新节点
			else if (EventType.NodeDataChanged == eventType) {
				System.out.println(logPrefix + &quot;节点数据更新&quot;);
				System.out.println(&quot;我看看走不走这里........&quot;);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(logPrefix + &quot;数据内容: &quot; + this.readData(PARENT_PATH, true));
			} 
			//更新子节点
			else if (EventType.NodeChildrenChanged == eventType) {
				System.out.println(logPrefix + &quot;子节点变更&quot;);
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(logPrefix + &quot;子节点列表：&quot; + this.getChildren(PARENT_PATH, true));
			} 
			//删除节点
			else if (EventType.NodeDeleted == eventType) {
				System.out.println(logPrefix + &quot;节点 &quot; + path + &quot; 被删除&quot;);
			}
			else ;
		} 
		else if (KeeperState.Disconnected == keeperState) {
			System.out.println(logPrefix + &quot;与ZK服务器断开连接&quot;);
		} 
		else if (KeeperState.AuthFailed == keeperState) {
			System.out.println(logPrefix + &quot;权限检查失败&quot;);
		} 
		else if (KeeperState.Expired == keeperState) {
			System.out.println(logPrefix + &quot;会话失效&quot;);
		}
		else ;

		System.out.println(&quot;--------------------------------------------&quot;);

	}

	/**
	 * &amp;lt;B&amp;gt;方法名称：&amp;lt;/B&amp;gt;测试zookeeper监控&amp;lt;BR&amp;gt;
	 * &amp;lt;B&amp;gt;概要说明：&amp;lt;/B&amp;gt;主要测试watch功能&amp;lt;BR&amp;gt;
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {

		//建立watcher
		ZooKeeperWatcher zkWatch = new ZooKeeperWatcher();
		//创建连接
		zkWatch.createConnection(CONNECTION_ADDR, SESSION_TIMEOUT);
		//System.out.println(zkWatch.zk.toString());
		
		Thread.sleep(1000);
		
		// 清理节点
		zkWatch.deleteAllTestPath();
		
		if (zkWatch.createPath(PARENT_PATH, System.currentTimeMillis() + &quot;&quot;)) {
			
			Thread.sleep(1000);
			
			
			// 读取数据
			System.out.println(&quot;---------------------- read parent ----------------------------&quot;);
			//zkWatch.readData(PARENT_PATH, true);
			
			// 读取子节点
			System.out.println(&quot;---------------------- read children path ----------------------------&quot;);
			zkWatch.getChildren(PARENT_PATH, true);

			// 更新数据
			zkWatch.writeData(PARENT_PATH, System.currentTimeMillis() + &quot;&quot;);
			
			Thread.sleep(1000);
			
			// 创建子节点
			zkWatch.createPath(CHILDREN_PATH, System.currentTimeMillis() + &quot;&quot;);
			
			Thread.sleep(1000);
			
			zkWatch.writeData(CHILDREN_PATH, System.currentTimeMillis() + &quot;&quot;);
		}
		
		Thread.sleep(50000);
		// 清理节点
		zkWatch.deleteAllTestPath();
		Thread.sleep(1000);
		zkWatch.releaseConnection();
	}

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;集群下的zookeeper&quot;&gt;集群下的zookeeper&lt;/h1&gt;

&lt;p&gt;ZKWatcher.java类&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.zookeeper.cluster;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class ZKWatcher implements Watcher {

	/** zk变量 */
	private ZooKeeper zk = null;
	
	/** 父节点path */
	static final String PARENT_PATH = &quot;/super&quot;;
	
	/** 信号量设置，用于等待zookeeper连接建立之后 通知阻塞程序继续向下执行 */
	private CountDownLatch connectedSemaphore = new CountDownLatch(1);
	
	private List&amp;lt;String&amp;gt; cowaList = new CopyOnWriteArrayList&amp;lt;String&amp;gt;();
	
	
	/** zookeeper服务器地址 */
	public static final String CONNECTION_ADDR = &quot;192.168.80.88:2181,192.168.80.87:2181,192.168.80.86:2181&quot;;
	/** 定义session失效时间 */
	public static final int SESSION_TIMEOUT = 30000;
	
	public ZKWatcher() throws Exception{
		zk = new ZooKeeper(CONNECTION_ADDR, SESSION_TIMEOUT, this);
		System.out.println(&quot;开始连接ZK服务器&quot;);
		connectedSemaphore.await();
	}


	@Override
	public void process(WatchedEvent event) {
		// 连接状态
		KeeperState keeperState = event.getState();
		// 事件类型
		EventType eventType = event.getType();
		// 受影响的path
		String path = event.getPath();
		System.out.println(&quot;受影响的path : &quot; + path);
		
		
		if (KeeperState.SyncConnected == keeperState) {
			// 成功连接上ZK服务器
			if (EventType.None == eventType) {
				System.out.println(&quot;成功连接上ZK服务器&quot;);
				connectedSemaphore.countDown();
				try {
					if(this.zk.exists(PARENT_PATH, false) == null){
						this.zk.create(PARENT_PATH, &quot;root&quot;.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);		 
					}
					List&amp;lt;String&amp;gt; paths = this.zk.getChildren(PARENT_PATH, true);
					for (String p : paths) {
						System.out.println(p);
						this.zk.exists(PARENT_PATH + &quot;/&quot; + p, true);
					}
				} catch (KeeperException | InterruptedException e) {
					e.printStackTrace();
				}		
			} 
			//创建节点
			else if (EventType.NodeCreated == eventType) {
				System.out.println(&quot;节点创建&quot;);
				try {
					this.zk.exists(path, true);
				} catch (KeeperException | InterruptedException e) {
					e.printStackTrace();
				}
			} 
			//更新节点
			else if (EventType.NodeDataChanged == eventType) {
				System.out.println(&quot;节点数据更新&quot;);
				try {
					//update nodes  call function
					this.zk.exists(path, true);
				} catch (KeeperException | InterruptedException e) {
					e.printStackTrace();
				}
			} 
			//更新子节点
			else if (EventType.NodeChildrenChanged == eventType) {
				System.out.println(&quot;子节点 ... 变更&quot;);
				try {
					List&amp;lt;String&amp;gt; paths = this.zk.getChildren(path, true);
					if(paths.size() &amp;gt;= cowaList.size()){
						paths.removeAll(cowaList);
						for(String p : paths){
							this.zk.exists(path + &quot;/&quot; + p, true);
							//this.zk.getChildren(path + &quot;/&quot; + p, true);
							System.out.println(&quot;这个是新增的子节点 : &quot; + path + &quot;/&quot; + p);
							//add new nodes  call function
						}
						cowaList.addAll(paths);
					} else {
						cowaList = paths;
					}
					System.out.println(&quot;cowaList: &quot; + cowaList.toString());
					System.out.println(&quot;paths: &quot; + paths.toString());
					
				} catch (KeeperException | InterruptedException e) {
					e.printStackTrace();
				}
			} 
			//删除节点
			else if (EventType.NodeDeleted == eventType) {
				System.out.println(&quot;节点 &quot; + path + &quot; 被删除&quot;);
				try {
					//delete nodes  call function
					this.zk.exists(path, true);
				} catch (KeeperException | InterruptedException e) {
					e.printStackTrace();
				}
			}
			else ;
		} 
		else if (KeeperState.Disconnected == keeperState) {
			System.out.println(&quot;与ZK服务器断开连接&quot;);
		} 
		else if (KeeperState.AuthFailed == keeperState) {
			System.out.println(&quot;权限检查失败&quot;);
		} 
		else if (KeeperState.Expired == keeperState) {
			System.out.println(&quot;会话失效&quot;);
		}
		else ;

		System.out.println(&quot;--------------------------------------------&quot;);
	}
	
	

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Client1.java类&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.zookeeper.cluster;

import bjsxt.zookeeper.cluster.ZKWatcher;

public class Client1 {

	public static void main(String[] args) throws Exception{
		
		ZKWatcher myWatcher = new ZKWatcher();
		Thread.sleep(100000000);
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Client2.java类&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.zookeeper.cluster;

import bjsxt.zookeeper.cluster.ZKWatcher;

public class Client1 {

	public static void main(String[] args) throws Exception{
		
		ZKWatcher myWatcher = new ZKWatcher();
		Thread.sleep(100000000);
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Test.java类&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.zookeeper.cluster;

import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class Test {


	/** zookeeper地址 */
	static final String CONNECT_ADDR = &quot;192.168.1.106:2181,192.168.1.107:2181,192.168.1.108:2181&quot;;
	/** session超时时间 */
	static final int SESSION_OUTTIME = 2000;//ms 
	/** 信号量，阻塞程序执行，用于等待zookeeper连接成功，发送成功信号 */
	static final CountDownLatch connectedSemaphore = new CountDownLatch(1);
	
	public static void main(String[] args) throws Exception{
		
		ZooKeeper zk = new ZooKeeper(CONNECT_ADDR, SESSION_OUTTIME, new Watcher(){
			@Override
			public void process(WatchedEvent event) {
				//获取事件的状态
				KeeperState keeperState = event.getState();
				EventType eventType = event.getType();
				//如果是建立连接
				if(KeeperState.SyncConnected == keeperState){
					if(EventType.None == eventType){
						//如果建立连接成功，则发送信号量，让后续阻塞程序向下执行
						connectedSemaphore.countDown();
						System.out.println(&quot;zk 建立连接&quot;);
					}
				}
			}
		});

		//进行阻塞
		connectedSemaphore.await();
		
//		//创建子节点
//		zk.create(&quot;/super/c1&quot;, &quot;c1&quot;.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
		//创建子节点
//		zk.create(&quot;/super/c2&quot;, &quot;c2&quot;.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
		//创建子节点
		zk.create(&quot;/super/c3&quot;, &quot;c3&quot;.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
		//创建子节点
//		zk.create(&quot;/super/c4&quot;, &quot;c4&quot;.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
		
//		zk.create(&quot;/super/c4/c44&quot;, &quot;c44&quot;.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
		
		//获取节点信息
//		byte[] data = zk.getData(&quot;/testRoot&quot;, false, null);
//		System.out.println(new String(data));
//		System.out.println(zk.getChildren(&quot;/testRoot&quot;, false));
		
		//修改节点的值
//		zk.setData(&quot;/super/c1&quot;, &quot;modify c1&quot;.getBytes(), -1);
//		zk.setData(&quot;/super/c2&quot;, &quot;modify c2&quot;.getBytes(), -1);
//		byte[] data = zk.getData(&quot;/super/c2&quot;, false, null);
//		System.out.println(new String(data));		
		
//		//判断节点是否存在
//		System.out.println(zk.exists(&quot;/super/c3&quot;, false));
//		//删除节点
//		zk.delete(&quot;/super/c3&quot;, -1);
		
		zk.close();
	
	
	
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;zookeeper的aclauth&quot;&gt;zookeeper的ACL(AUTH)&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package qs.zookeeper.auth;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
/**
 * Zookeeper 节点授权
 */
public class ZookeeperAuth implements Watcher {

	/** 连接地址 */
	final static String CONNECT_ADDR = &quot;192.168.80.88:2181&quot;;
	/** 测试路径 */
	final static String PATH = &quot;/testAuth&quot;;
	final static String PATH_DEL = &quot;/testAuth/delNode&quot;;
	/** 认证类型 */
	final static String authentication_type = &quot;digest&quot;;
	/** 认证正确方法 */
	final static String correctAuthentication = &quot;123456&quot;;
	/** 认证错误方法 */
	final static String badAuthentication = &quot;654321&quot;;
	
	static ZooKeeper zk = null;
	/** 计时器 */
	AtomicInteger seq = new AtomicInteger();
	/** 标识 */
	private static final String LOG_PREFIX_OF_MAIN = &quot;【Main】&quot;;
	
	private CountDownLatch connectedSemaphore = new CountDownLatch(1);
	
	@Override
	public void process(WatchedEvent event) {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		if (event==null) {
			return;
		}
		// 连接状态
		KeeperState keeperState = event.getState();
		// 事件类型
		EventType eventType = event.getType();
		// 受影响的path
		String path = event.getPath();
		
		String logPrefix = &quot;【Watcher-&quot; + this.seq.incrementAndGet() + &quot;】&quot;;

		System.out.println(logPrefix + &quot;收到Watcher通知&quot;);
		System.out.println(logPrefix + &quot;连接状态:\t&quot; + keeperState.toString());
		System.out.println(logPrefix + &quot;事件类型:\t&quot; + eventType.toString());
		if (KeeperState.SyncConnected == keeperState) {
			// 成功连接上ZK服务器
			if (EventType.None == eventType) {
				System.out.println(logPrefix + &quot;成功连接上ZK服务器&quot;);
				connectedSemaphore.countDown();
			} 
		} else if (KeeperState.Disconnected == keeperState) {
			System.out.println(logPrefix + &quot;与ZK服务器断开连接&quot;);
		} else if (KeeperState.AuthFailed == keeperState) {
			System.out.println(logPrefix + &quot;权限检查失败&quot;);
		} else if (KeeperState.Expired == keeperState) {
			System.out.println(logPrefix + &quot;会话失效&quot;);
		}
		System.out.println(&quot;--------------------------------------------&quot;);
	}
	/**
	 * 创建ZK连接
	 * 
	 * @param connectString
	 *ZK服务器地址列表
	 * @param sessionTimeout
	 *Session超时时间
	 */
	public void createConnection(String connectString, int sessionTimeout) {
		this.releaseConnection();
		try {
			zk = new ZooKeeper(connectString, sessionTimeout, this);
			//添加节点授权
			zk.addAuthInfo(authentication_type,correctAuthentication.getBytes());
			System.out.println(LOG_PREFIX_OF_MAIN + &quot;开始连接ZK服务器&quot;);
			//倒数等待
			connectedSemaphore.await();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 关闭ZK连接
	 */
	public void releaseConnection() {
		if (this.zk!=null) {
			try {
				this.zk.close();
			} catch (InterruptedException e) {
			}
		}
	}
	
	/**
	 * 
	 * &amp;lt;B&amp;gt;方法名称：&amp;lt;/B&amp;gt;测试函数&amp;lt;BR&amp;gt;
	 * &amp;lt;B&amp;gt;概要说明：&amp;lt;/B&amp;gt;测试认证&amp;lt;BR&amp;gt;
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {
		
		ZookeeperAuth testAuth = new ZookeeperAuth();
		testAuth.createConnection(CONNECT_ADDR,2000);
		List&amp;lt;ACL&amp;gt; acls = new ArrayList&amp;lt;ACL&amp;gt;(1);
		for (ACL ids_acl : Ids.CREATOR_ALL_ACL) {
			acls.add(ids_acl);
		}

		try {
			zk.create(PATH, &quot;init content&quot;.getBytes(), acls, CreateMode.PERSISTENT);
			System.out.println(&quot;使用授权key：&quot; + correctAuthentication + &quot;创建节点：&quot;+ PATH + &quot;, 初始内容是: init content&quot;);
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			zk.create(PATH_DEL, &quot;will be deleted! &quot;.getBytes(), acls, CreateMode.PERSISTENT);
			System.out.println(&quot;使用授权key：&quot; + correctAuthentication + &quot;创建节点：&quot;+ PATH_DEL + &quot;, 初始内容是: init content&quot;);
		} catch (Exception e) {
			e.printStackTrace();
		}

		// 获取数据
		getDataByNoAuthentication();
		getDataByBadAuthentication();
		getDataByCorrectAuthentication();

		// 更新数据
		updateDataByNoAuthentication();
		updateDataByBadAuthentication();
		updateDataByCorrectAuthentication();

		// 删除数据
		deleteNodeByBadAuthentication();
		deleteNodeByNoAuthentication();
		deleteNodeByCorrectAuthentication();
		//
		Thread.sleep(1000);
		
		deleteParent();
		//释放连接
		testAuth.releaseConnection();
	}
	/** 获取数据：采用错误的密码 */
	static void getDataByBadAuthentication() {
		String prefix = &quot;[使用错误的授权信息]&quot;;
		try {
			ZooKeeper badzk = new ZooKeeper(CONNECT_ADDR, 2000, null);
			//授权
			badzk.addAuthInfo(authentication_type,badAuthentication.getBytes());
			Thread.sleep(2000);
			System.out.println(prefix + &quot;获取数据：&quot; + PATH);
			System.out.println(prefix + &quot;成功获取数据：&quot; + badzk.getData(PATH, false, null));
		} catch (Exception e) {
			System.err.println(prefix + &quot;获取数据失败，原因：&quot; + e.getMessage());
		}
	}

	/** 获取数据：不采用密码 */
	static void getDataByNoAuthentication() {
		String prefix = &quot;[不使用任何授权信息]&quot;;
		try {
			System.out.println(prefix + &quot;获取数据：&quot; + PATH);
			ZooKeeper nozk = new ZooKeeper(CONNECT_ADDR, 2000, null);
			Thread.sleep(2000);
			System.out.println(prefix + &quot;成功获取数据：&quot; + nozk.getData(PATH, false, null));
		} catch (Exception e) {
			System.err.println(prefix + &quot;获取数据失败，原因：&quot; + e.getMessage());
		}
	}

	/** 采用正确的密码 */
	static void getDataByCorrectAuthentication() {
		String prefix = &quot;[使用正确的授权信息]&quot;;
		try {
			System.out.println(prefix + &quot;获取数据：&quot; + PATH);
			
			System.out.println(prefix + &quot;成功获取数据：&quot; + zk.getData(PATH, false, null));
		} catch (Exception e) {
			System.out.println(prefix + &quot;获取数据失败，原因：&quot; + e.getMessage());
		}
	}

	/**
	 * 更新数据：不采用密码
	 */
	static void updateDataByNoAuthentication() {

		String prefix = &quot;[不使用任何授权信息]&quot;;

		System.out.println(prefix + &quot;更新数据： &quot; + PATH);
		try {
			ZooKeeper nozk = new ZooKeeper(CONNECT_ADDR, 2000, null);
			Thread.sleep(2000);
			Stat stat = nozk.exists(PATH, false);
			if (stat!=null) {
				nozk.setData(PATH, prefix.getBytes(), -1);
				System.out.println(prefix + &quot;更新成功&quot;);
			}
		} catch (Exception e) {
			System.err.println(prefix + &quot;更新失败，原因是：&quot; + e.getMessage());
		}
	}

	/**
	 * 更新数据：采用错误的密码
	 */
	static void updateDataByBadAuthentication() {

		String prefix = &quot;[使用错误的授权信息]&quot;;

		System.out.println(prefix + &quot;更新数据：&quot; + PATH);
		try {
			ZooKeeper badzk = new ZooKeeper(CONNECT_ADDR, 2000, null);
			//授权
			badzk.addAuthInfo(authentication_type,badAuthentication.getBytes());
			Thread.sleep(2000);
			Stat stat = badzk.exists(PATH, false);
			if (stat!=null) {
				badzk.setData(PATH, prefix.getBytes(), -1);
				System.out.println(prefix + &quot;更新成功&quot;);
			}
		} catch (Exception e) {
			System.err.println(prefix + &quot;更新失败，原因是：&quot; + e.getMessage());
		}
	}

	/**
	 * 更新数据：采用正确的密码
	 */
	static void updateDataByCorrectAuthentication() {

		String prefix = &quot;[使用正确的授权信息]&quot;;

		System.out.println(prefix + &quot;更新数据：&quot; + PATH);
		try {
			Stat stat = zk.exists(PATH, false);
			if (stat!=null) {
				zk.setData(PATH, prefix.getBytes(), -1);
				System.out.println(prefix + &quot;更新成功&quot;);
			}
		} catch (Exception e) {
			System.err.println(prefix + &quot;更新失败，原因是：&quot; + e.getMessage());
		}
	}

	/**
	 * 不使用密码 删除节点
	 */
	static void deleteNodeByNoAuthentication() throws Exception {

		String prefix = &quot;[不使用任何授权信息]&quot;;

		try {
			System.out.println(prefix + &quot;删除节点：&quot; + PATH_DEL);
			ZooKeeper nozk = new ZooKeeper(CONNECT_ADDR, 2000, null);
			Thread.sleep(2000);
			Stat stat = nozk.exists(PATH_DEL, false);
			if (stat!=null) {
				nozk.delete(PATH_DEL,-1);
				System.out.println(prefix + &quot;删除成功&quot;);
			}
		} catch (Exception e) {
			System.err.println(prefix + &quot;删除失败，原因是：&quot; + e.getMessage());
		}
	}

	/**
	 * 采用错误的密码删除节点
	 */
	static void deleteNodeByBadAuthentication() throws Exception {

		String prefix = &quot;[使用错误的授权信息]&quot;;

		try {
			System.out.println(prefix + &quot;删除节点：&quot; + PATH_DEL);
			ZooKeeper badzk = new ZooKeeper(CONNECT_ADDR, 2000, null);
			//授权
			badzk.addAuthInfo(authentication_type,badAuthentication.getBytes());
			Thread.sleep(2000);
			Stat stat = badzk.exists(PATH_DEL, false);
			if (stat!=null) {
				badzk.delete(PATH_DEL, -1);
				System.out.println(prefix + &quot;删除成功&quot;);
			}
		} catch (Exception e) {
			System.err.println(prefix + &quot;删除失败，原因是：&quot; + e.getMessage());
		}
	}

	/**
	 * 使用正确的密码删除节点
	 */
	static void deleteNodeByCorrectAuthentication() throws Exception {

		String prefix = &quot;[使用正确的授权信息]&quot;;

		try {
			System.out.println(prefix + &quot;删除节点：&quot; + PATH_DEL);
			Stat stat = zk.exists(PATH_DEL, false);
			if (stat!=null) {
				zk.delete(PATH_DEL, -1);
				System.out.println(prefix + &quot;删除成功&quot;);
			}
		} catch (Exception e) {
			System.out.println(prefix + &quot;删除失败，原因是：&quot; + e.getMessage());
		}
	}

	/**
	 * 使用正确的密码删除节点
	 */
	static void deleteParent() throws Exception {
		try {
			Stat stat = zk.exists(PATH_DEL, false);
			if (stat == null) {
				zk.delete(PATH, -1);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Qing Shuang</name></author><category term="Zookeeper" /><summary type="html">增删改查节点 package qs.zookeeper.base; import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooDefs.Ids; public class ZookeeperBase { /** zookeeper地址 */ static final String CONNECT_ADDR = &quot;192.168.80.88:2181,192.168.80.87:2181,192.168.80.86:2181&quot;; /** session超时时间 */ static final int SESSION_OUTTIME = 2000;//ms /** 信号量，阻塞程序执行，用于等待zookeeper连接成功，发送成功信号 */ static final CountDownLatch connectedSemaphore = new CountDownLatch(1); public static void main(String[] args) throws Exception{ ZooKeeper zk = new ZooKeeper(CONNECT_ADDR, SESSION_OUTTIME, new Watcher(){ @Override public void process(WatchedEvent event) { //获取事件的状态 KeeperState keeperState = event.getState(); EventType eventType = event.getType(); //如果是建立连接 //KeeperState.ConnectedReadOnly //``KeeperState.Disconnected //``KeeperState.Expired //KeeperState.NoSyncConnected //``KeeperState.SyncConnected //KeeperState.Unknown if(KeeperState.SyncConnected == keeperState){ if(EventType.None == eventType){ //如果建立连接成功，则发送信号量，让后续阻塞程序向下执行 connectedSemaphore.countDown(); System.out.println(&quot;zk 建立连接&quot;); } } } }); //进行阻塞 connectedSemaphore.await(); System.out.println(&quot;..&quot;); //创建父节点 zk.create(&quot;/testRoot&quot;, &quot;testRoot&quot;.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT,); //创建子节点 zk.create(&quot;/testRoot/children&quot;, &quot;children data&quot;.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); //获取节点洗信息 byte[] data = zk.getData(&quot;/testRoot&quot;, false, null); System.out.println(new String(data)); System.out.println(zk.getChildren(&quot;/testRoot&quot;, false)); //修改节点的值 zk.setData(&quot;/testRoot&quot;, &quot;modify data root&quot;.getBytes(), -1); byte[] data = zk.getData(&quot;/testRoot&quot;, false, null); System.out.println(new String(data)); //判断节点是否存在 System.out.println(zk.exists(&quot;/testRoot/children&quot;, false)); //删除节点 zk.delete(&quot;/testRoot/children&quot;, -1); System.out.println(zk.exists(&quot;/testRoot/children&quot;, false)); zk.close(); } }</summary></entry><entry><title type="html">ActiveMQ传送机制以及ACK机制详解</title><link href="https://ronaldoshuang.github.io//2017/05/04/ActiveMessenger-3/" rel="alternate" type="text/html" title="ActiveMQ传送机制以及ACK机制详解" /><published>2017-05-04T00:00:00+00:00</published><updated>2017-05-04T00:00:00+00:00</updated><id>https://ronaldoshuang.github.io//2017/05/04/ActiveMessenger-3</id><content type="html" xml:base="https://ronaldoshuang.github.io//2017/05/04/ActiveMessenger-3/">&lt;h1 id=&quot;activemq消息传送机制&quot;&gt;ActiveMQ消息传送机制&lt;/h1&gt;

&lt;p&gt;一条消息的生命周期如下-一条消息从producer端发出之后，一旦被broker正确保存，那么它将会被consumer消费，然后ACK，broker端才会删除；不过当消息过期或者存储设备溢出时，也会终结它：
&lt;img src=&quot;https://ronaldoshuang.github.io//images/posts/ActiveMessenger/ActiveMessenger-3-1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img src=&quot;https://ronaldoshuang.github.io//images/posts/ActiveMessenger/ActiveMessenger-3-2.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这张图片中简单的描述了:1)producer端如何发送消息 2) consumer端如何消费消息 3) broker端如何调度。如果用文字来描述图示中的概念，恐怕一言难尽。图示中，提及到prefetchAck，以及消息同步、异步发送的基本逻辑；这对你了解下文中的ACK机制将有很大的帮助。&lt;/p&gt;

&lt;h1 id=&quot;optimizeack&quot;&gt;optimizeACK&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;“可优化的ACK”，这是ActiveMQ对于consumer在消息消费时，对消息ACK的优化选项，也是consumer端最重要的优化参数之一，你可以通过如下方式开启:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;optimizeack与prefetchsize的设置&quot;&gt;optimizeACK与prefetchSize的设置&lt;/h2&gt;

&lt;p&gt;1) 在brokerUrl中增加如下查询字符串：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;String brokerUrl = &quot;tcp://localhost:61616?&quot; +   
   &quot;jms.optimizeAcknowledge=true&quot; +   
   &quot;&amp;amp;jms.optimizeAcknowledgeTimeOut=30000&quot; +   
   &quot;&amp;amp;jms.redeliveryPolicy.maximumRedeliveries=6&quot;;  
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerUrl); 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2) 在destinationUri中，增加如下查询字符串：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;String queueName = &quot;test-queue?customer.prefetchSize=100&quot;;  
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);  
Destination queue = session.createQueue(queueName); 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们需要在brokerUrl指定optimizeACK选项，在destinationUri中指定prefetchSize(预获取)选项，其中brokerUrl参数选项是全局的，即当前factory下所有的connection/session/consumer都会默认使用这些值；而destinationUri中的选项，只会在使用此destination的consumer实例中有效；如果同时指定，brokerUrl中的参数选项值将会被覆盖。&lt;/p&gt;

&lt;p&gt;optimizeAck表示是否开启“优化ACK”，只有在为true的情况下，prefetchSize(下文中将会简写成prefetch)以及optimizeAcknowledgeTimeout参数才会有意义。此处需要注意”optimizeAcknowledgeTimeout”选项只能在brokerUrl中配置。&lt;/p&gt;

&lt;p&gt;prefetch值建议在destinationUri中指定，因为在brokerUrl中指定比较繁琐；在brokerUrl中，queuePrefetchSize和topicPrefetchSize都需要单独设定：”&amp;amp;jms.prefetchPolicy.queuePrefetch=12&amp;amp;jms.prefetchPolicy.topicPrefetch=12”等来逐个指定。&lt;/p&gt;

&lt;h2 id=&quot;prefetchsize设置的数值大小的意义&quot;&gt;prefetchSize设置的数值大小的意义&lt;/h2&gt;

&lt;p&gt;如果prefetchACK为true，那么prefetch必须大于0；当prefetchACK为false时，你可以指定prefetch为0以及任意大小的正数。不过，当prefetch=0是，表示consumer将使用PULL(拉取)的方式从broker端获取消息，broker端将不会主动push消息给client端，直到client端发送PullCommand时；当prefetch&amp;gt;0时，就开启了broker push模式，此后只要当client端消费且ACK了一定的消息之后，会立即push给client端多条消息。&lt;/p&gt;

&lt;h3 id=&quot;对于receive方法&quot;&gt;对于receive方法&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;当consumer端使用receive()方法同步获取消息时，prefetch可以为0和任意正值；当prefetch=0时，那么receive()方法将会首先发送一个PULL指令并阻塞，直到broker端返回消息为止，这也意味着消息只能逐个获取(类似于Request&amp;lt;-&amp;gt;Response)，这也是Activemq中PULL消息模式；当prefetch &amp;gt; 0时，broker端将会批量push给client 一定数量的消息(&amp;lt;= prefetch),client端会把这些消息(unconsumedMessage)放入到本地的队列中，只要此队列有消息，那么receive方法将会立即返回，当一定量的消息ACK之后，broker端会继续批量push消息给client端
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;对于messagelistener接口的onmessage&quot;&gt;对于MessageListener接口的onMEssage()&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;当consumer端使用MessageListener异步获取消息时，这就需要开发设定的prefetch值必须 &amp;gt;=1,即至少为1；在异步消费消息模式中，设定prefetch=0,是相悖的，也将获得一个Exception。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;optimizeack和prefeth达成一个高效的消息消费模型&quot;&gt;optimizeACK和prefeth达成一个高效的消息消费模型&lt;/h1&gt;

&lt;p&gt;批量获取消息，并“延迟”确认(ACK)。prefetch表达了“批量获取”消息的语义，broker端主动的批量push多条消息给client端，总比client多次发送PULL指令然后broker返回一条消息的方式要优秀很多，它不仅减少了client端在获取消息时阻塞的次数和阻塞的时间，还能够大大的减少网络开支。optimizeACK表达了“延迟确认”的语义(ACK时机)，client端在消费消息后暂且不发送ACK，而是把它缓存下来(pendingACK)，等到这些消息的条数达到一定阀值时，只需要通过一个ACK指令把它们全部确认；这比对每条消息都逐个确认，在性能上要提高很多。由此可见，prefetch优化了消息传送的性能，optimizeACK优化了消息确认的性能。&lt;/p&gt;

&lt;h2 id=&quot;optimizeack--prefetch如何设置提高性能&quot;&gt;optimizeACK + prefetch如何设置提高性能&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;1) 如果consumer端消费速度很慢(对消息的处理是耗时的)，过大的prefetchSize，并不能有效的提升性能，反而不利于consumer端的负载均衡(只针对queue)；按照良好的设计准则，当consumer消费速度很慢时，我们通常会部署多个consumer客户端，并使用较小的prefetch，同时关闭optimizeACK，可以让消息在多个consumer间“负载均衡”(即均匀的发送给每个consumer)；如果较大的prefetchSize，将会导致broker一次性push给client大量的消息，但是这些消息需要很久才能ACK(消息积压)，而且在client故障时，还会导致这些消息的重发。&lt;/p&gt;

  &lt;p&gt;2) 如果consumer端消费速度很快，但是producer端生成消息的速率较慢，比如生产者10秒钟生成10条消息，但是consumer一秒就能消费完毕，而且我们还部署了多个consumer！！这种场景下，建议开启optimizeACK，但是需要设置的prefetchSize不能过大；这样可以保证每个consumer都能有”活干”，否则将会出现一个consumer非常忙碌，但是其他consumer几乎收不到消息。&lt;/p&gt;

  &lt;p&gt;3) 如果消息很重要，特别是不愿意接收到”redelivery“的消息，那么我们需要将optimizeACK=false，prefetchSize=1&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;注意：既然optimizeACK是”延迟“确认，那么就引入一种潜在的风险：在消息被消费之后还没有来得及确认时，client端发生故障，那么这些消息就有可能会被重新发送给其他consumer，那么这种风险就需要client端能够容忍“重复”消息。&lt;/p&gt;

&lt;h2 id=&quot;optimizeack延时确认的前提&quot;&gt;optimizeACK延时确认的前提&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;即使当optimizeACK为true，也只会当session的ACK模式为AUTO_ACKNOWLEDGE时才会生效，即在其他类型的ACK模式时consumer端仍然不会“延迟确认”&lt;/p&gt;

  &lt;p&gt;当consumer.optimizeACK有效时，如果客户端已经消费但尚未确认的消息(deliveredMessage)达到prefetch * 0.65，consumer端将会自动进行ACK；同时如果离上一次ACK的时间间隔，已经超过”optimizeAcknowledgeTimout”毫秒，也会导致自动进行ACK。&lt;/p&gt;

  &lt;p&gt;此外简单的补充一下，批量确认消息时，只需要在ACK指令中指明“firstMessageId”和“lastMessageId”即可，即消息区间，那么broker端就知道此consumer(根据consumerId识别)需要确认哪些消息。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;ack模式与类型介绍&quot;&gt;ACK模式与类型介绍&lt;/h1&gt;

&lt;h2 id=&quot;四种ack模式&quot;&gt;四种ACK模式&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;AUTO_ACKNOWLEDGE = 1自动确认
CLIENT_ACKNOWLEDGE = 2客户端手动确认   
DUPS_OK_ACKNOWLEDGE = 3自动批量确认
SESSION_TRANSACTED = 0事务提交并确认
此外AcitveMQ补充了一个自定义的ACK模式:
INDIVIDUAL_ACKNOWLEDGE = 4单条消息确认
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;auto_acknowledge&quot;&gt;AUTO_ACKNOWLEDGE&lt;/h3&gt;
&lt;p&gt;自动确认,这就意味着消息的确认时机将有consumer择机确认.”择机确认”似乎充满了不确定性,这也意味着,开发者必须明确知道”择机确认”的具体时机,否则将有可能导致消息的丢失,或者消息的重复接收.那么在ActiveMQ中,AUTO_ACKNOWLEDGE是如何运作的呢?
自动确认,这就意味着消息的确认时机将有consumer择机确认.”择机确认”似乎充满了不确定性,这也意味着,开发者必须明确知道”择机确认”的具体时机,否则将有可能导致消息的丢失,或者消息的重复接收.那么在ActiveMQ中,AUTO_ACKNOWLEDGE是如何运作的呢?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1) 对于consumer而言，optimizeAcknowledge属性只会在AUTO_ACK模式下有效。

 

2) 其中DUPS_ACKNOWLEGE也是一种潜在的AUTO_ACK,只是确认消息的条数和时间上有所不同。

 

3) 在“同步”(receive)方法返回message之前,会检测optimizeACK选项是否开启，如果没有
   开启，此单条消息将立即确认，所以在这种情况下，message返回之后，如果开发者在处理
   message过程中出现异常，会导致此消息也不会redelivery,即&quot;潜在的消息丢失&quot;；如果
   开启了optimizeACK，则会在unAck数量达到prefetch * 0.65时确认，当然我们可以指
   定prefetchSize = 1来实现逐条消息确认。

 

4) 在&quot;异步&quot;(messageListener)方式中,将会首先调用listener.onMessage(message),此
   后再ACK,如果onMessage方法异常,将导致client端补充发送一个ACK_TYPE为
   REDELIVERED_ACK_TYPE确认指令；如果onMessage方法正常,消息将会正常确认
   (STANDARD_ACK_TYPE)。此外需要注意，消息的重发次数是有限制的，每条消息中都会包
   含“redeliveryCounter”计数器，用来表示此消息已经被重发的次数，如果重发次数达到
   阀值，将会导致发送一个ACK_TYPE为POSION_ACK_TYPE确认指令,这就导致broker端认为
   此消息无法消费,此消息将会被删除或者迁移到&quot;dead letter&quot;通道中。



因此当我们使用messageListener方式消费消息时，通常建议在onMessage方法中使用try-
catch,这样可以在处理消息出错时记录一些信息，而不是让consumer不断去重发消息；如果你
没有使用try-catch,就有可能会因为异常而导致消息重复接收的问题,需要注意你的onMessage
方法中逻辑是否能够兼容对重复消息的判断。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://ronaldoshuang.github.io//images/posts/ActiveMessenger/ActiveMessenger-3-3.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;client_acknowledge&quot;&gt;CLIENT_ACKNOWLEDGE&lt;/h3&gt;

&lt;p&gt;客户端手动确认，这就意味着AcitveMQ将不会“自作主张”的为你ACK任何消息，开发者需要自己择机确认。在此模式下，开发者需要需要关注几个方法：1) message.acknowledge()，2) ActiveMQMessageConsumer.acknowledege()，3) ActiveMQSession.acknowledge()；其1)和3)是等效的，将当前session中所有consumer中尚未ACK的消息都一起确认，2)只会对当前consumer中那些尚未确认的消息进行确认。开发者可以在合适的时机必须调用一次上述方法。为了避免混乱，对于这种ACK模式下，建议一个session下只有一个consumer。&lt;/p&gt;

&lt;p&gt;我们通常会在基于Group(消息分组)情况下会使用CLIENT_ACKNOWLEDGE，我们将在一个group的消息序列接受完毕之后确认消息(组)；不过当你认为消息很重要，只有当消息被正确处理之后才能确认时，也可以使用此模式  。&lt;/p&gt;

&lt;p&gt;如果开发者忘记调用acknowledge方法，将会导致当consumer重启后，会接受到重复消息，因为对于broker而言，那些尚未真正ACK的消息被视为“未消费”。&lt;/p&gt;

&lt;p&gt;开发者可以在当前消息处理成功之后，立即调用message.acknowledge()方法来”逐个”确认消息，这样可以尽可能的减少因网络故障而导致消息重发的个数；当然也可以处理多条消息之后，间歇性的调用acknowledge方法来一次确认多条消息，减少ack的次数来提升consumer的效率，不过这仍然是一个利弊权衡的问题。&lt;/p&gt;

&lt;p&gt;除了message.acknowledge()方法之外，ActiveMQMessageConumser.acknowledge()和ActiveMQSession.acknowledge()也可以确认消息，只不过前者只会确认当前consumer中的消息。其中sesson.acknowledge()和message.acknowledge()是等效的。&lt;/p&gt;

&lt;p&gt;无论是“同步”/“异步”，ActiveMQ都不会发送STANDARD_ACK_TYPE，直到message.acknowledge()调用。如果在client端未确认的消息个数达到prefetchSize * 0.5时，会补充发送一个ACK_TYPE为DELIVERED_ACK_TYPE的确认指令，这会触发broker端可以继续push消息到client端。(参看PrefetchSubscription.acknwoledge方法)&lt;/p&gt;

&lt;p&gt;在broker端，针对每个Consumer，都会保存一个因为”DELIVERED_ACK_TYPE”而“拖延”的消息个数，这个参数为prefetchExtension，事实上这个值不会大于prefetchSize * 0.5,因为Consumer端会严格控制DELIVERED_ACK_TYPE指令发送的时机(参见ActiveMQMessageConsumer.ackLater方法)，broker端通过“prefetchExtension”与prefetchSize互相配合，来决定即将push给client端的消息个数，count = prefetchExtension + prefetchSize - dispatched.size()，其中dispatched表示已经发送给client端但是还没有“STANDARD_ACK_TYPE”的消息总量；由此可见，在CLIENT_ACK模式下，足够快速的调用acknowledge()方法是决定consumer端消费消息的速率；如果client端因为某种原因导致acknowledge方法未被执行，将导致大量消息不能被确认，broker端将不会push消息，事实上client端将处于“假死”状态，而无法继续消费消息。我们要求client端在消费1.5*prefetchSize个消息之前，必须acknowledge()一次；通常我们总是每消费一个消息调用一次，这是一种良好的设计。&lt;/p&gt;

&lt;p&gt;此外需要额外的补充一下：所有ACK指令都是依次发送给broker端，在CLIET_ACK模式下，消息在交付给listener之前，都会首先创建一个DELIVERED_ACK_TYPE的ACK指令，直到client端未确认的消息达到”prefetchSize * 0.5”时才会发送此ACK指令，如果在此之前，开发者调用了acknowledge()方法，会导致消息直接被确认(STANDARD_ACK_TYPE)。broker端通常会认为“DELIVERED_ACK_TYPE”确认指令是一种“slow consumer”信号，如果consumer不能及时的对消息进行acknowledge而导致broker端阻塞，那么此consumer将会被标记为“slow”，此后queue中的消息将会转发给其他Consumer。&lt;/p&gt;

&lt;h3 id=&quot;dups_ok_acknowledge&quot;&gt;DUPS_OK_ACKNOWLEDGE&lt;/h3&gt;

&lt;p&gt;消息可重复”确认，意思是此模式下，可能会出现重复消息，并不是一条消息需要发送多次ACK才行。它是一种潜在的”AUTO_ACK”确认机制，为批量确认而生，而且具有“延迟”确认的特点。对于开发者而言，这种模式下的代码结构和AUTO_ACKNOWLEDGE一样，不需要像CLIENT_ACKNOWLEDGE那样调用acknowledge()方法来确认消息。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1) 在ActiveMQ中，如果在Destination是Queue通道，我们真的可以认为DUPS_OK_ACK就是“AUTO_ACK + optimizeACK + (prefetch &amp;gt; 0)”这种情况，在确认时机上几乎完全一致；此外在此模式下，如果prefetchSize =1 或者没有开启optimizeACK，也会导致消息逐条确认，从而失去批量确认的特性。

 

2) 如果Destination为Topic，DUPS_OK_ACKNOWLEDGE才会产生JMS规范中诠释的意义，即无论optimizeACK是否开启，都会在消费的消息个数&amp;gt;=prefetch * 0.5时，批量确认(STANDARD_ACK_TYPE),在此过程中，不会发送DELIVERED_ACK_TYPE的确认指令,这是1)和AUTO_ACK的最大的区别。

 

这也意味着，当consumer故障重启后，那些尚未ACK的消息会重新发送过来。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;session_transacted&quot;&gt;SESSION_TRANSACTED&lt;/h3&gt;

&lt;p&gt;当session使用事务时，就是使用此模式。在事务开启之后，和session.commit()之前，所有消费的消息，要么全部正常确认，要么全部redelivery。这种严谨性，通常在基于GROUP(消息分组)或者其他场景下特别适合。在SESSION_TRANSACTED模式下，optimizeACK并不能发挥任何效果,因为在此模式下，optimizeACK会被强制设定为false，不过prefetch仍然可以决定DELIVERED_ACK_TYPE的发送时机。&lt;/p&gt;

&lt;p&gt;因为Session非线程安全，那么当前session下所有的consumer都会共享同一个transactionContext；同时建议，一个事务类型的Session中只有一个Consumer，以避免rollback()或者commit()方法被多个consumer调用而造成的消息混乱。&lt;/p&gt;

&lt;p&gt;当consumer接受到消息之后，首先检测TransactionContext是否已经开启，如果没有，就会开启并生成新的transactionId，并把信息发送给broker；此后将检测事务中已经消费的消息个数是否 &amp;gt;= prefetch * 0.5,如果大于则补充发送一个“DELIVERED_ACK_TYPE”的确认指令；这时就开始调用onMessage()方法，如果是同步(receive),那么即返回message。上述过程，和其他确认模式没有任何特殊的地方。&lt;/p&gt;

&lt;p&gt;当开发者决定事务可以提交时，必须调用session.commit()方法，commit方法将会导致当前session的事务中所有消息立即被确认；事务的确认过程中，首先把本地的deliveredMessage队列中尚未确认的消息全部确认(STANDARD_ACK_TYPE)；此后向broker发送transaction提交指令并等待broker反馈，如果broker端事务操作成功，那么将会把本地deliveredMessage队列清空，新的事务开始；如果broker端事务操作失败(此时broker已经rollback)，那么对于session而言，将执行inner-rollback，这个rollback所做的事情，就是将当前事务中的消息清空并要求broker重发(REDELIVERED_ACK_TYPE),同时commit方法将抛出异常。&lt;/p&gt;

&lt;p&gt;当session.commit方法异常时，对于开发者而言通常是调用session.rollback()回滚事务(事实上开发者不调用也没有问题)，当然你可以在事务开始之后的任何时机调用rollback(),rollback意味着当前事务的结束，事务中所有的消息都将被重发。需要注意，无论是inner-rollback还是调用session.rollback()而导致消息重发，都会导致message.redeliveryCounter计数器增加，最终都会受限于brokerUrl中配置的”jms.redeliveryPolicy.maximumRedeliveries”,如果rollback的次数过多，而达到重发次数的上限时，消息将会被DLQ(dead letter)。&lt;/p&gt;

&lt;h3 id=&quot;individual_acknowledge&quot;&gt;INDIVIDUAL_ACKNOWLEDGE&lt;/h3&gt;

&lt;p&gt;单条消息确认，这种确认模式，我们很少使用，它的确认时机和CLIENT_ACKNOWLEDGE几乎一样，当消息消费成功之后，需要调用message.acknowledege来确认此消息(单条)，而CLIENT_ACKNOWLEDGE模式先message.acknowledge()方法将导致整个session中所有消息被确认(批量确认)。&lt;/p&gt;

&lt;h2 id=&quot;ack_type&quot;&gt;ACK_TYPE&lt;/h2&gt;

&lt;p&gt;Client端指定了ACK模式,但是在Client与broker在交换ACK指令的时候,还需要告知ACK_TYPE,ACK_TYPE表示此确认指令的类型，不同的ACK_TYPE将传递着消息的状态，broker可以根据不同的ACK_TYPE对消息进行不同的操作。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;DELIVERED_ACK_TYPE = 0消息&quot;已接收&quot;，但尚未处理结束
STANDARD_ACK_TYPE = 2&quot;标准&quot;类型,通常表示为消息&quot;处理成功&quot;，broker端可以删除消息了
POSION_ACK_TYPE = 1消息&quot;错误&quot;,通常表示&quot;抛弃&quot;此消息，比如消息重发多次后，都无法正确处理时，消息将会被删除或者DLQ(死信队列)
REDELIVERED_ACK_TYPE = 3消息需&quot;重发&quot;，比如consumer处理消息时抛出了异常，broker稍后会重新发送此消息
INDIVIDUAL_ACK_TYPE = 4表示只确认&quot;单条消息&quot;,无论在任何ACK_MODE下
UNMATCHED_ACK_TYPE = 5在Topic中，如果一条消息在转发给“订阅者”时，发现此消息不符合Selector过滤条件，那么此消息将 不会转发给订阅者，消息将会被存储引擎删除(相当于在Broker上确认了消息)。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://ronaldoshuang.github.io//images/posts/ActiveMessenger/ActiveMessenger-3-4.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;消息确认的时机&quot;&gt;消息确认的时机&lt;/h2&gt;

&lt;p&gt;Consumer消费消息的风格有2种: 同步/异步..使用consumer.receive()就是同步，使用messageListener就是异步；在同一个consumer中，我们不能同时使用这2种风格，比如在使用listener的情况下，当调用receive()方法将会获得一个Exception。两种风格下，消息确认时机有所不同。&lt;/p&gt;

&lt;h3 id=&quot;对于receive方法-1&quot;&gt;对于receive方法&lt;/h3&gt;

&lt;p&gt;同步调用时，在消息从receive方法返回之前，就已经调用了ACK；因此如果Client端没有处理成功，此消息将丢失(可能重发，与ACK模式有关)。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//receive伪代码---过程  
Message message = sessionMessageQueue.dequeue();  
if(message != null){  
ack(message);  
}  
return message 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;对于onmessage方法&quot;&gt;对于onMessage方法&lt;/h3&gt;

&lt;p&gt;基于异步调用时，消息的确认是在onMessage方法返回之后，如果onMessage方法异常，会导致消息不能被ACK，会触发重发。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//基于listener  
Session session = connection.getSession(consumerId);  
sessionQueueBuffer.enqueue(message);  
Runnable runnable = new Ruannale(){  
run(){  
Consumer consumer = session.getConsumer(consumerId);  
Message md = sessionQueueBuffer.dequeue();  
try{  
consumer.messageListener.onMessage(md);  
ack(md);//  
}catch(Exception e){  
redelivery();//sometime，not all the time;  
}  
}  
//session中将采取线程池的方式，分发异步消息  
//因此同一个session中多个consumer可以并行消费  
threadPool.execute(runnable);  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Qing Shuang</name></author><category term="ActiveMessenger" /><summary type="html">ActiveMQ消息传送机制</summary></entry><entry><title type="html">ActiveMQ中文参考手册解读</title><link href="https://ronaldoshuang.github.io//2017/05/04/ActiveMessenger-4/" rel="alternate" type="text/html" title="ActiveMQ中文参考手册解读" /><published>2017-05-04T00:00:00+00:00</published><updated>2017-05-04T00:00:00+00:00</updated><id>https://ronaldoshuang.github.io//2017/05/04/ActiveMessenger-4</id><content type="html" xml:base="https://ronaldoshuang.github.io//2017/05/04/ActiveMessenger-4/">&lt;h1 id=&quot;1-jms&quot;&gt;1 JMS&lt;/h1&gt;

&lt;p&gt;在介绍ActiveMQ之前，首先简要介绍一下JMS规范。&lt;/p&gt;

&lt;h2 id=&quot;11-jms的基本构件&quot;&gt;1.1 JMS的基本构件&lt;/h2&gt;

&lt;h3 id=&quot;111-连接工厂&quot;&gt;1．1．1 连接工厂&lt;/h3&gt;

&lt;p&gt;连接工厂是客户用来创建连接的对象，例如ActiveMQ提供的ActiveMQConnectionFactory。&lt;/p&gt;

&lt;h3 id=&quot;112-连接&quot;&gt;1．1．2 连接&lt;/h3&gt;

&lt;p&gt;JMS Connection封装了客户与JMS提供者之间的一个虚拟的连接。&lt;/p&gt;

&lt;h3 id=&quot;113-会话&quot;&gt;1．1．3 会话&lt;/h3&gt;

&lt;p&gt;JMS Session是生产和消费消息的一个单线程上下文。会话用于创建消息生产者（producer）、消息消费者（consumer）和消息（message）等。会话提供了一个事务性的上下文，在这个上下文中，一组发送和接收被组合到了一个原子操作中。&lt;/p&gt;

&lt;h3 id=&quot;114-目的地&quot;&gt;1．1．4 目的地&lt;/h3&gt;

&lt;p&gt;目的地是客户用来指定它生产的消息的目标和它消费的消息的来源的对象。JMS1.0.2规范中定义了两种消息传递域：点对点（PTP）消息传递域和发布/订阅消息传递域。 点对点消息传递域的特点如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• 每个消息只能有一个消费者。
• 消息的生产者和消费者之间没有时间上的相关性。无论消费者在生产者发送消息的时候是否处于运行状态，它都可以提取消息。
发布/订阅消息传递域的特点如下：
• 每个消息可以有多个消费者。
• 生产者和消费者之间有时间上的相关性。订阅一个主题的消费者只能消费自它订阅之后发布的消息。JMS规范允许客户创建持久订阅，这在一定程度上放松了时间上的相关性要求。持久订阅允许消费者消费它在未处于激活状态时发送的消息。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在点对点消息传递域中，目的地被成为队列（queue）；在发布/订阅消息传递域中，目的地被成为主题（topic）。&lt;/p&gt;

&lt;h3 id=&quot;115-消息生产者&quot;&gt;1．1．5 消息生产者&lt;/h3&gt;

&lt;p&gt;消息生产者是由会话创建的一个对象，用于把消息发送到一个目的地。&lt;/p&gt;

&lt;h3 id=&quot;116-消息消费者&quot;&gt;1．1．6 消息消费者&lt;/h3&gt;

&lt;p&gt;消息消费者是由会话创建的一个对象，它用于接收发送到目的地的消息。消息的消费可以采用以下两种方法之一：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• 同步消费。通过调用消费者的receive方法从目的地中显式提取消息。receive方法可以一直阻塞到消息到达。
• 异步消费。客户可以为消费者注册一个消息监听器，以定义在消息到达时所采取的动作。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;117-消息&quot;&gt;1．1．7 消息&lt;/h3&gt;

&lt;p&gt;JMS消息由以下三部分组成的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• 消息头。每个消息头字段都有相应的getter和setter方法。
• 消息属性。如果需要除消息头字段以外的值，那么可以使用消息属性。
• 消息体。JMS定义的消息类型有TextMessage、MapMessage、BytesMessage、StreamMessage和ObjectMessage。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;12-jms的可靠性机制&quot;&gt;1．2 JMS的可靠性机制&lt;/h2&gt;

&lt;h3 id=&quot;121-确认-jms消息&quot;&gt;1．2．1 确认 JMS消息&lt;/h3&gt;

&lt;p&gt;只有在被确认之后，才认为已经被成功地消费了。消息的成功消费通常包含三个阶段：客户接收消息、客户处理消息和消息被确认。 在事务性会话中，当一个事务被提交的时候，确认自动发生。在非事务性会话中，消息何时被确认取决于创建会话时的应答模式（acknowledgement mode）。该参数有以下三个可选值：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• Session.AUTO_ACKNOWLEDGE。当客户成功的从receive方法返回的时候，或者从MessageListener.onMessage方法成功返回的时候，会话自动确认客户收到的消息。
• Session.CLIENT_ACKNOWLEDGE。客户通过消息的acknowledge方法确认消息。需要注意的是，在这种模式中，确认是在会话层上进行：确认一个被消费的消息将自动确认所有已被会话消费的消息。例如，如果一个消息消费者消费了10个消息，然后确认第5个消息，那么所有10个消息都被确认。
• Session.DUPS_ACKNOWLEDGE。该选择只是会话迟钝的确认消息的提交。如果JMS Provider失败，那么可能会导致一些重复的消息。如果是重复的消息，那么JMS Provider必须把消息头的JMSRedelivered字段设置为true。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;122-持久性&quot;&gt;1．2．2 持久性&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;JMS 支持以下两种消息提交模式：
• PERSISTENT。指示JMS Provider持久保存消息，以保证消息不会因为JMS Provider的失败而丢失。
• NON_PERSISTENT。不要求JMS Provider持久保存消息。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;123-优先级&quot;&gt;1．2．3 优先级&lt;/h3&gt;

&lt;p&gt;可以使用消息优先级来指示JMS Provider首先提交紧急的消息。优先级分10个级别，从0（最低）到9（最高）。如果不指定优先级，默认级别是4。需要注意的是，JMS Provider并不一定保证按照优先级的顺序提交消息。&lt;/p&gt;

&lt;h3 id=&quot;124-消息过期&quot;&gt;1．2．4 消息过期&lt;/h3&gt;

&lt;p&gt;可以设置消息在一定时间后过期，默认是永不过期。&lt;/p&gt;

&lt;h3 id=&quot;125-临时目的地&quot;&gt;1．2．5 临时目的地&lt;/h3&gt;

&lt;p&gt;可以通过会话上的createTemporaryQueue方法和createTemporaryTopic方法来创建临时目的地。它们的存在时间只限于创建它们的连接所保持的时间。只有创建该临时目的地的连接上的消息消费者才能够从临时目的地中提取消息。&lt;/p&gt;

&lt;h3 id=&quot;126-持久订阅&quot;&gt;1．2．6 持久订阅&lt;/h3&gt;

&lt;p&gt;首先消息生产者必须使用PERSISTENT提交消息。客户可以通过会话上的createDurableSubscriber方法来创建一个持久订阅，该方法的第一个参数必须是一个topic，第二个参数是订阅的名称。 JMS Provider会存储发布到持久订阅对应的topic上的消息。如果最初创建持久订阅的客户或者任何其它客户使用相同的连接工厂和连接的客户ID、相同的主题和相同的订阅名再次调用会话上的createDurableSubscriber方法，那么该持久订阅就会被激活。JMS Provider会象客户发送客户处于非激活状态时所发布的消息。 持久订阅在某个时刻只能有一个激活的订阅者。持久订阅在创建之后会一直保留，直到应用程序调用会话上的unsubscribe方法。(生产者发送的消息持久化保存，当消费者存活时，消息再发生给消费者)&lt;/p&gt;

&lt;h3 id=&quot;127-本地事务&quot;&gt;1．2．7 本地事务&lt;/h3&gt;

&lt;p&gt;在一个JMS客户端，可以使用本地事务来组合消息的发送和接收。JMS Session接口提供了commit和rollback方法。事务提交意味着生产的所有消息被发送，消费的所有消息被确认；事务回滚意味着生产的所有消息被销毁，消费的所有消息被恢复并重新提交，除非它们已经过期。 事务性的会话总是牵涉到事务处理中，commit或rollback方法一旦被调用，一个事务就结束了，而另一个事务被开始。关闭事务性会话将回滚其中的事务。 需要注意的是，如果使用请求/回复机制，即发送一个消息，同时希望在同一个事务中等待接收该消息的回复，那么程序将被挂起，因为知道事务提交，发送操作才会真正执行。 需要注意的还有一个，消息的生产和消费不能包含在同一个事务中。&lt;/p&gt;

&lt;h2 id=&quot;13-jms-规范的变迁&quot;&gt;1．3 JMS 规范的变迁&lt;/h2&gt;

&lt;p&gt;JMS的最新版本的是1.1。它和同1.0.2版本之间最大的差别是，JMS1.1通过统一的消息传递域简化了消息传递。这不仅简化了JMS API，也有利于开发人员灵活选择消息传递域，同时也有助于程序的重用和维护。 以下是不同消息传递域的相应接口：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;JMS公共				点对点域						发布/订阅域
ConnectionFactory	QueueConnectionFactory 		TopicConnectionFactory
Connection			QueueConnection 			TopicConnection
Destination			Queue 						Topic
Session				QueueSession 				TopicSession
MessageProducer		QueueSender 				TopicPublisher
MessageConsumer		QueueReceiver 				TopicSubscriber
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;2activemq&quot;&gt;2．ActiveMQ&lt;/h1&gt;

&lt;h2 id=&quot;21-broker&quot;&gt;2．1 Broker&lt;/h2&gt;

&lt;h3 id=&quot;211-运行broker&quot;&gt;2．1．1 运行Broker&lt;/h3&gt;
&lt;p&gt;ActiveMQ 5.0 的二进制发布包中bin目录中包含一个名为activemq的脚本，直接运行这个脚本就可以启动一个broker。 此外也可以通过Broker Configuration URI或Broker XBean URI对broker进行配置，以下是一些命令行参数的例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Example	Description
activemq	Runs a broker using the default 'xbean:activemq.xml' as the broker configuration file.
activemq xbean:myconfig.xml	Runs a broker using the file myconfig.xml as the broker configuration file that is located in the classpath.
activemq xbean:file:./conf/broker1.xml	Runs a broker using the file broker1.xml as the broker configuration file that is located in the relative file path ./conf/broker1.xml
activemq xbean:file:C:/ActiveMQ/conf/broker2.xml	Runs a broker using the file broker2.xml as the broker configuration file that is located in the absolute file path C:/ActiveMQ/conf/broker2.xml
activemq broker:(tcp://localhost:61616, tcp://localhost:5000)?useJmx=true	Runs a broker with two transport connectors and JMX enabled.
activemq broker:(tcp://localhost:61616, network:tcp://localhost:5000)?persistent=false	Runs a broker with 1 transport connector and 1 network connector with persistence disabled.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;212-嵌入式broker&quot;&gt;2．1．2 嵌入式Broker&lt;/h3&gt;

&lt;p&gt;可以通过在应用程序中以编码的方式启动broker，例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. BrokerService broker = new BrokerService();
2. broker.addConnector(&quot;tcp://localhost:61616&quot;);
3. broker.start();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果需要启动多个broker，那么需要为broker设置一个名字。例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. BrokerService broker = new BrokerService();
2. broker.setName(&quot;fred&quot;);
3. broker.addConnector(&quot;tcp://localhost:61616&quot;);
4. broker.start();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果希望在同一个JVM内访问这个broker，那么可以使用VM Transport，URI是：vm://brokerName。关于更多的broker属性，可以参考Apache的官方文档。 此外，也可以通过BrokerFactory来创建broker，例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. BrokerService broker = BrokerFactory.createBroker(new URI(someURI));
someURI的可选值如下：

URI scheme	Example	Description
xbean:	xbean:activemq.xml	Searches the classpath for an XML document with the given URI (activemq.xml in this case) which will then be used as the Xml Configuration

file:	file:foo/bar/activemq.xml	Loads the given file (in this example foo/bar/activemq.xml) as the Xml Configuration

broker:	broker:tcp://localhost:61616	Uses the Broker Configuration URI to configure the broker

当使用XBean的配置方式的时候，需要指定一个xml配置文件，例如：
Java代码
1. BrokerService broker = BrokerFactory.createBroker(new URI(&quot;xbean:com/test/activemq.xml&quot;));
使用Spring的配置方式如下：
Xml代码
1. &amp;lt;bean id=&quot;broker&quot; class=&quot;org.apache.activemq.xbean.BrokerFactoryBean&quot;&amp;gt;
2. 	&amp;lt;property name=&quot;config&quot; value=&quot;classpath:org/apache/activemq/xbean/activemq.xml&quot; /&amp;gt;
3. 	&amp;lt;property name=&quot;start&quot; value=&quot;true&quot; /&amp;gt;
4. &amp;lt;/bean&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;213-监控broker&quot;&gt;2．1．3 监控Broker&lt;/h3&gt;

&lt;h4 id=&quot;2131-jmx&quot;&gt;2．1．3．1 JMX&lt;/h4&gt;
&lt;p&gt;在使用JMX监控broker之前，首先要启用broker的JMX监控功能，例如在配置文件中设置useJmx=”true”，如下：
Xml代码&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&amp;lt;broker useJmx=”true” brokerName=”broker1”&amp;gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;managementContext&gt;
&lt;/managementContext&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;managementContext createConnector=&quot;true&quot; /&gt;
  &lt;/li&gt;
  &lt;li&gt;&amp;lt;/managementContext&amp;gt;&lt;/li&gt;
  &lt;li&gt;…&lt;/li&gt;
  &lt;li&gt;&amp;lt;/broker&amp;gt;
接下来运行JDK自带的jconsole。在运行了jconsole后，它会弹出对话框来选择需要连接到的agent。如果是在启动broker的主机上运行jconsole，那么ActiveMQ broker会出现在jconsole的Local 标签中。如果要连接到远程的broker，那么可以在Advanced标签中指定JMX URL，以下是一个连接到本机的JMX URL： service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi
在jconsole的MBeans标签中，可以查看详细信息，也可以执行相应的operation。需要注意的是，在jconsole连接到broker的时候，并不需要输入用户名和密码，如果这存在潜在的安全问题，那么就需要为JMX Connector配置密码保护（需要使用1.5以上版本的JDK）。
首先要禁止ActiveMQ创建自己的connector，例如：
Xml代码&lt;/li&gt;
  &lt;li&gt;&amp;lt;broker xmlns=”http://activemq.org/config/1.0” brokerName=”localhost”useJmx=”true”&amp;gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;managementContext&gt;
&lt;/managementContext&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;managementContext createConnector=&quot;false&quot; /&gt;
  &lt;/li&gt;
  &lt;li&gt;&amp;lt;/managementContext&amp;gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&amp;lt;/broker&amp;gt;
然后在ActiveMQ的conf目录下创建一个访问控制文件和密码文件，如下： 
conf/jmx.access：&lt;/p&gt;

    &lt;p&gt;# The “monitorRole” role has readonly access. 
 # The “controlRole” role has readwrite access. 
 monitorRole readonly 
 controlRole readwrite
 conf/jmx.password： 
 # The “monitorRole” role has password “abc123”. 
 # The “controlRole” role has password “abcd1234”. 
 monitorRole abc123 
 controlRole abcd1234
然后修改ActiveMQ的bin目录下activemq的启动脚本，查找包含”SUNJMX=”的一行如下：&lt;/p&gt;

    &lt;p&gt;REM set SUNJMX=-Dcom.sun.management.jmxremote.port=1616
 -Dcom.sun.management.jmxremote.authenticate=false
 -Dcom.sun.management.jmxremote.ssl=false 
 把它替换成 
 set SUNJMX=-Dcom.sun.management.jmxremote.port=1616
 -Dcom.sun.management.jmxremote.authenticate=true 
 -Dcom.sun.management.jmxremote.ssl=false 
 -Dcom.sun.management.jmxremote.password.file=%ACTIVEMQ_BASE%/conf/jmx.password 
 -Dcom.sun.management.jmxremote.access.file=%ACTIVEMQ_BASE%/conf/jmx.access&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最后重启ActiveMQ和jconsole，这时候需要强制login。如果在启动activemq的过程中出现以下错误，那么需要为这个文件增加访问控制。Windows平台上的具体解决方法请参考如下网址：
http://java.sun.com/j2se/1.5.0/docs/guide/management/security-windows.html 
Error: Password file read access must be restricted: 
D:\apache-activemq-5.0.0\bin../conf/jmx.password&lt;/p&gt;

&lt;h4 id=&quot;2132-web-console&quot;&gt;2．1．3．2 Web Console&lt;/h4&gt;

&lt;p&gt;Web Console被集成到了ActiveMQ的二进制发布包中，因此缺省访问http://localhost:8161/admin即可访问Web Console。 在配置文件中，可以通过修改nioConnector的port属性来修改Web console的缺省端口：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;jetty xmlns=&quot;http://mortbay.com/schemas/jetty/1.0&quot;&amp;gt;
2. &amp;lt;connectors&amp;gt;
3. &amp;lt;nioConnector port=&quot;8161&quot; /&amp;gt;
4. &amp;lt;/connectors&amp;gt;
5. ...
6. &amp;lt;/jetty&amp;gt;
7.  出于安全性或者可靠性的考虑，Web Console 可以被部署到不同于ActiveMQ的进程中。例如把activemq-web-console.war部署到一个单独的web容器中（Tomcat，Jetty等）。在ActiveMQ5.0的二进制发布包中不包含activemq-web-console.war，因此需要下载ActiveMQ的源码，然后进入到${activemq.base}/src/activemq-web-console目录中执行mvn instanll。如果一切正常，那么缺省会在${activemq.base}/src/activemq-web-console/target目录中生成activemq-web-console-5.0.0.war。然后将activemq-web-console-5.0.0.war拷贝到Tomcat的webapps目录中，并重命名成activemq-web-console.war。 需要注意的是，要将activemq-all-5.0.0.jar拷贝到WEB-INF\lib目录中（可能还需要拷贝jms.jar）。还要为Tomcat设置以下五个系统属性（修改catalina.bat文件）：

set JAVA_OPTS=%JAVA_OPTS% -Dwebconsole.type=&quot;properties&quot; 
set JAVA_OPTS=%JAVA_OPTS% -Dwebconsole.jms.url=&quot;tcp://localhost:61616&quot; 
set JAVA_OPTS=%JAVA_OPTS% -Dwebconsole.jmx.url=&quot;service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi&quot; 
set JAVA_OPTS=%JAVA_OPTS% -Dwebconsole.jmx.role=&quot;&quot; 
set JAVA_OPTS=%JAVA_OPTS% -Dwebconsole.jmx.password=&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果JMX没有配置密码保护，那么webconsole.jmx.role和webconsole.jmx.password设置成”“即可。如果broker被配置成了Master/Slave模式，那么可以配置成使用failover transport，例如：
-Dwebconsole.jms.url=failover:(tcp://serverA:61616,tcp://serverB:61616)
顺便说一下，由于webconsole.type 属性是properties，因此实际上起作用的Web Console的配置文件是WEB-INF/ webconsole-properties.xml。最后启动被监控的ActiveMQ，访问http://localhost:8080/activemq-web-console/，查看显示是否正常。&lt;/p&gt;

&lt;h4 id=&quot;2133-advisory-message公告消息&quot;&gt;2．1．3．3 Advisory Message（公告消息）&lt;/h4&gt;

&lt;p&gt;ActiveMQ 支持Advisory Messages，它允许你通过标准的JMS 消息来监控系统。目前的Advisory Messages支持：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• consumers, producers and connections starting and stopping
• temporary destinations being created and destroyed
• messages expiring on topics and queues
• brokers sending messages to destinations with no consumers.
• connections starting and stopping
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Advisory Messages可以被想象成某种的管理通道，通过它你可以得到关于JMS Provider、producers、consumers和destinations的信息。Advisory topics都使用ActiveMQ.Advisory.这个前缀，以下是目前支持的topics：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Client based advisories
Advisory Topics	Description
ActiveMQ.Advisory.Connection	Connection start &amp;amp; stop messages
ActiveMQ.Advisory.Producer.Queue	Producer start &amp;amp; stop messages on a Queue
ActiveMQ.Advisory.Producer.Topic	Producer start &amp;amp; stop messages on a Topic
ActiveMQ.Advisory.Consumer.Queue	Consumer start &amp;amp; stop messages on a Queue
ActiveMQ.Advisory.Consumer.Topic	Consumer start &amp;amp; stop messages on a Topic
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在消费者启动/停止的Advisory Messages的消息头中有个consumerCount属性，他用来指明目前desination上活跃的consumer的数量。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Destination and Message based advisories
Advisory Topics	Description
ActiveMQ.Advisory.Queue	Queue create &amp;amp; destroy
ActiveMQ.Advisory.Topic	Topic create &amp;amp; destroy
ActiveMQ.Advisory.TempQueue	Temporary Queue create &amp;amp; destroy
ActiveMQ.Advisory.TempTopic	Temporary Topic create &amp;amp; destroy
ActiveMQ.Advisory.Expired.Queue	Expired messages on a Queue
ActiveMQ.Advisory.Expired.Topic	Expired messages on a Topic
ActiveMQ.Advisory.NoConsumer.Queue	No consumer is available to process messages being sent on a Queue
ActiveMQ.Advisory.NoConsumer.Topic	No consumer is available to process messages being sent on a Topic
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;以上的这些destnations都可以用来作为前缀，在其后面追加其它的重要信息，例如topic、queue、clientID、producderID和consumerID等。这令你可以利用Wildcards 和 Selectors 来过滤Advisory Messages（关于Wildcard和Selector会在稍后介绍）。
&lt;strong&gt;例如，如果你希望订阅FOO.BAR这个queue上Consumer的start/stop的消息，那么可以订阅ActiveMQ.Advisory.Consumer.Queue.FOO.BAR；如果希望订阅所有queue上Consumer的start/stop消息，那么可以订阅ActiveMQ.Advisory.Consumer.Queue.&amp;gt;；如果希望订阅所有queue或者topic上Consumer的start/stop消息，那么可以订阅ActiveMQ.Advisory.Consumer. &amp;gt;。
org.apache.activemq.advisory.AdvisorySupport类上有如下的helper methods，用来在程序中得到advisory destination objects。&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. AdvisorySupport.getConsumerAdvisoryTopic()
2. AdvisorySupport.getProducerAdvisoryTopic()
3. AdvisorySupport.getDestinationAdvisoryTopic()
4. AdvisorySupport.getExpiredTopicMessageAdvisoryTopic()
5. AdvisorySupport.getExpiredQueueMessageAdvisoryTopic()
6. AdvisorySupport.getNoTopicConsumersAdvisoryTopic()
7. AdvisorySupport.getNoQueueConsumersAdvisoryTopic()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;以下是段使用Advisory Messages的程序代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. Destination advisoryDestination = AdvisorySupport.getProducerAdvisoryTopic(destination)
2. MessageConsumer consumer = session.createConsumer(advisoryDestination);
3. consumer.setMessageListener(this);
4. ...
5. public void onMessage(Message msg){
6. if (msg instanceof ActiveMQMessage){
7. try {
8. ActiveMQMessage aMsg = (ActiveMQMessage)msg;
9. ProducerInfo prod = (ProducerInfo) aMsg.getDataStructure();
10. } catch (JMSException e) {
11. log.error(&quot;Failed to process message: &quot; + msg);
12. }
13. }
14.}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2134-command-agent不懂&quot;&gt;2．1．3．4 Command Agent（不懂）&lt;/h4&gt;

&lt;p&gt;在介绍Command Agent前首先简要介绍一下XMPP(Jabber)协议，XMPP是一种基于XML的即时通信协议，它由Jabber软件基金会开发。在配置文件中通过增加transportConnector来支持XMPP协议：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;broker xmlns=&quot;http://activemq.org/config/1.0&quot;&amp;gt;
2. &amp;lt;transportConnectors&amp;gt;
3. ...
4. &amp;lt;transportConnector name=&quot;xmpp&quot; uri=&quot;xmpp://localhost:61222&quot;/&amp;gt;
5. &amp;lt;/transportConnectors&amp;gt;
6. &amp;lt;/broker&amp;gt;
7.  ActiveMQ提供了ActiveMQ messages和XMPP之间的双向桥接：

• 如果客户加入了一个聊天室，那么这个聊天室的名字会被映射到一个JMS topic。
• 尝试在聊天室内发送消息会导致一个JMS消息被发送到这个topic。
• 呆在一个聊天室中意味着这将保持一个对相应JMS topic的订阅。因此发送到这个topic的JMS消息也会被发送到聊天室。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;推荐XMPP客户端Spark(http://www.igniterealtime.org/)。
从4.2版本起，ActiveMQ支持Command Agent。在配置文件中，通过设置commandAgent来启用&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Command Agent：
Xml代码
1. &amp;lt;beans&amp;gt;
2. &amp;lt;broker useJmx=&quot;true&quot; xmlns=&quot;http://activemq.org/config/1.0&quot;&amp;gt;
3. ...
4. &amp;lt;/broker&amp;gt;
5. &amp;lt;commandAgent xmlns=&quot;http://activemq.org/config/1.0&quot;/&amp;gt;
6. &amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;启用了Command Agent的broker上会有一个来自Command Agent的连接，它同时订阅topic： ActiveMQ.Agent。在你启动XMPP客户端，加入到ActiveMQ.Agent聊天室后，就可以同broker进行交谈了。通过在XMPP客户端中键入help，可以得到帮助信息。 需要注意的是，ActiveMQ5.0版本有个小bug，如果broker没有采用缺省的用户名和密码，那么Command Agent便无法正常启动。Apache官方文档说，此bug已经被修正，预定在5.2.0版本上体现。修改方式如下：
Xml代码&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;commandAgent xmlns=&quot;http://activemq.org/config/1.0&quot; brokerUser=&quot;user&quot; brokerPassword=&quot;passward&quot; /&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;2135-visualization-plugin&quot;&gt;2．1．3．5 Visualization Plugin&lt;/h4&gt;

&lt;p&gt;ActiveMQ支持以broker插件的形式生成DOT文件(可以用agrviewer来查看)，以图表的方式描述connections、sessions、producers、consumers、destinations等信息。配置方式如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;broker xmlns=&quot;http://activemq.org/config/1.0&quot; brokerName=&quot;localhost&quot; useJmx=&quot;true&quot;&amp;gt;
2. ...
3. &amp;lt;plugins&amp;gt;
4. &amp;lt;connectionDotFilePlugin file=&quot;connection.dot&quot;/&amp;gt;
5. &amp;lt;destinationDotFilePlugin file=&quot;destination.dot&quot;/&amp;gt;
6. &amp;lt;/plugins&amp;gt;
7. &amp;lt;/broker&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;需要注意的是，笔者认为ActiveMQ5.0版本的Visualization Plugin尚不稳定，存在诸多问题。例如：如果使用connectionDotFilePlugin，那么brokerName必须是localhost；如果使用destinationDotFilePlugin可能会导致ArrayStoreException。&lt;/p&gt;

&lt;h2 id=&quot;22-transport如何实现客户端切换broker&quot;&gt;2．2 Transport（如何实现客户端切换broker）&lt;/h2&gt;

&lt;p&gt;ActiveMQ目前支持的transport有：VM Transport、TCP Transport、SSL Transport、Peer Transport、UDP Transport、Multicast Transport、HTTP and HTTPS Transport、Failover Transport、Fanout Transport、Discovery Transport、ZeroConf Transport等。以下简单介绍其中的几种，更多请参考Apache官方文档。&lt;/p&gt;

&lt;h3 id=&quot;221-vm-transport&quot;&gt;2．2．1 VM Transport&lt;/h3&gt;

&lt;p&gt;VM transport允许在VM内部通信，从而避免了网络传输的开销。这时候采用的连接不是socket连接，而是直接地方法调用。 第一个创建VM 连接的客户会启动一个embed VM broker，接下来所有使用相同的broker名称的VM连接都会使用这个broker。当这个broker上所有的连接都关闭的时候，这个broker也会自动关闭。 以下是配置语法：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vm://brokerName?transportOptions
例如：vm://broker1?marshal=false&amp;amp;broker.persistent=false
Transport Options的可选值如下：
Option Name	Default Value	Description
Marshal	false	If true, forces each command sent over the transport to be marshlled and unmarshlled using a WireFormat
wireFormat	default	The name of the WireFormat to use
wireFormat.*		All the properties with this prefix are used to configure the wireFormat
create	true	If the broker should be created on demand if it does not allready exist. Only supported in ActiveMQ 4.1
broker.*		All the properties with this prefix are used to configure the broker. See Configuring Wire Formats for more information

以下是高级配置语法：
vm:(broker:(tcp://localhost)?brokerOptions)?transportOptions
vm:broker:(tcp://localhost)?brokerOptions
例如：vm:(broker:(tcp://localhost:6000)?persistent=false)?marshal=false
   

使用配置文件的配置语法： vm://localhost?brokerConfig=xbean:activemq.xml 
例如：vm:// localhost?brokerConfig=xbean:com/test/activemq.xml
使用Spring的配置：
Xml代码
1. &amp;lt;bean id=&quot;broker&quot; class=&quot;org.apache.activemq.xbean.BrokerFactoryBean&quot;&amp;gt;
2.  &amp;lt;property name=&quot;config&quot; value=&quot;classpath:org/apache/activemq/xbean/activemq.xml&quot; /&amp;gt;
3.  &amp;lt;property name=&quot;start&quot; value=&quot;true&quot; /&amp;gt;
4. &amp;lt;/bean&amp;gt;
5.
6. &amp;lt;bean id=&quot;connectionFactory&quot; class=&quot;org.apache.activemq.ActiveMQConnectionFactory&quot; depends-on=&quot;broker&quot;&amp;gt;
7. 	&amp;lt;property name=&quot;brokerURL&quot; value=&quot;vm://localhost&quot;/&amp;gt;
8. &amp;lt;/bean&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果persistent是true，那么ActiveMQ会在当前目录下创建一个缺省值是activemq-data的目录用于持久化保存数据。需要注意的是，如果程序中启动了多个不同名字的VM broker，那么可能会有如下警告：Failed to start jmx connector: Cannot bind to URL [rmi://localhost:1099/jmxrmi]: javax.naming.NameAlreadyBoundException…可以通过在transportOptions中追加broker.useJmx=false来禁用JMX来避免这个警告。&lt;/p&gt;

&lt;h3 id=&quot;222-tcp-transport&quot;&gt;2．2．2 TCP Transport&lt;/h3&gt;

&lt;p&gt;TCP transport 允许客户端通过TCP socket连接到远程的broker。以下是配置语法： tcp://hostname:port?transportOptions Transport Options的可选值如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Option Name	Default Value	Description
minmumWireFormatVersion	0	The minimum version wireformat that is allowed
trace	false	Causes all commands that are sent over the transport to be logged
useLocalHost	true	When true, it causes the local machines name to resolve to &quot;localhost&quot;.
socketBufferSize	64 * 1024	Sets the socket buffer size in bytes
soTimeout	0	sets the socket timeout in milliseconds
connectionTimeout	30000	A non-zero value specifies the connection timeout in milliseconds. A zero value means wait forever for the connection to be established. Negative values are ignored.
wireFormat	default	The name of the WireFormat to use
wireFormat.*		All the properties with this prefix are used to configure the wireFormat. See Configuring Wire Formats for more information

例如：tcp://localhost:61616?trace=false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;223-failover-transport&quot;&gt;2．2．3 Failover Transport&lt;/h3&gt;

&lt;p&gt;Failover Transport是一种重新连接的机制，它工作于其它transport的上层，用于建立可靠的传输。它的配置语法允许制定任意多个复合的URI。Failover transport会自动选择其中的一个URI来尝试建立连接。如果没有成功，那么会选择一个其它的URI来建立一个新的连接。以下是配置语法： 
failover:(uri1,…,uriN)?transportOptions 
failover:uri1,…,uriN&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Transport Options的可选值如下：
Option Name	Default Value	Description
initialReconnectDelay	10	How long to wait before the first reconnect attempt (in ms)
maxReconnectDelay	30000	The maximum amount of time we ever wait between reconnect attempts (in ms)
useExponentialBackOff	true	Should an exponential backoff be used between reconnect attempts
backOffMultiplier	2	The exponent used in the exponential backoff attempts
maxReconnectAttempts	0	If not 0, then this is the maximum number of reconnect attempts before an error is sent back to the client
randomize	true	use a random algorithm to choose the URI to use for reconnect from the list provided
backup	false	initialize and hold a second transport connection - to enable fast failover

例如：failover:(tcp://localhost:61616,tcp://remotehost:61616)?initialReconnectDelay=100
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;224-discovery-transport&quot;&gt;2．2．4 Discovery transport&lt;/h3&gt;

&lt;p&gt;Discovery transport是可靠的tranport。它使用Discovery transport来定位用来连接的URI列表。以下是配置语法： 
discovery:(discoveryAgentURI)?transportOptions 
discovery:discoveryAgentURI&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Transport Options的可选值如下：
Option Name	Default Value	Description
initialReconnectDelay	10	How long to wait before the first reconnect attempt
maxReconnectDelay	30000	The maximum amount of time we ever wait between reconnect attempts
useExponentialBackOff	true	Should an exponential backoff be used btween reconnect attempts
backOffMultiplier	2	The exponent used in the exponential backoff attempts
maxReconnectAttempts	0	If not 0, then this is the maximum number of reconnect attempts before an error is sent back to the client

例：discovery:(multicast://default)?initialReconnectDelay=100 为了使用Discovery来发现broker，需要为broker启用discovery agent。 以下是XML配置文件中的一个例子：
Xml代码
1. &amp;lt;broker name=&quot;foo&quot;&amp;gt;
2. &amp;lt;transportConnectors&amp;gt;
3. &amp;lt;transportConnector uri=&quot;tcp://localhost:0&quot; discoveryUri=&quot;multicast://default&quot;/&amp;gt;
4. &amp;lt;/transportConnectors&amp;gt;
5. ...
6. &amp;lt;/broker&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在使用Failover Transport或Discovery transport等能够自动重连的transport的时候，需要注意的是：设想有两个broker，它们都启用AMQ Message
Store作为持久化存储，有一个producer和一个consumer连接到某个queue。当因其中一个broker失效时而切换到另一个broker的时候，如果失效的broker的queue中还有未被consumer消费的消息，那么这个queue里的消息仍然滞留在失效broker的中，直到失效的broker被修复并重新切换回这个被修复的broker后，之前被保留的消息才会被consumer消费掉。如果被处理的消息有时序限制，那么应用程序就需要处理这个问题。另外也可以通过ActiveMQ集群来解决这个问题。&lt;/p&gt;

&lt;p&gt;在transport重连的时候，可以在connection上注册TransportListener来获得回调，例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. (ActiveMQConnection)connection).addTransportListener(new TransportListener() {
2. public void onCommand(Object cmd) {
3. }
4.
5. public void onException(IOException exp) {
6. }
7.
8. public void transportInterupted() {
9. // The transport has suffered an interruption from which it hopes to recover.
10. }
11.
12. public void transportResumed() {
13. // The transport has resumed after an interruption.
14. }
15.});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;23-持久化&quot;&gt;2．3 持久化&lt;/h2&gt;
&lt;h3 id=&quot;231-amq-message-store&quot;&gt;2．3．1 AMQ Message Store()&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;一句话理解：使用data log保存一组消息，如果文件中所有的消息都被成功消费，那么这个data log文件会被删除或者归档。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AMQ Message Store是ActiveMQ5.0缺省的持久化存储。Message commands被保存到transactional journal（由rolling data logs组成）。Messages被保存到data logs中，同时被reference store进行索引以提高存取速度。Date logs由一些单独的data log文件组成，缺省的文件大小是32M，如果某个消息的大小超过了data log文件的大小，那么可以修改配置以增加data log文件的大小。如果某个data log文件中所有的消息都被成功消费了，那么这个data log文件将会被标记，以便在下一轮的清理中被删除或者归档。以下是其配置的一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;broker brokerName=&quot;broker&quot; persistent=&quot;true&quot; useShutdownHook=&quot;false&quot;&amp;gt;
2. &amp;lt;persistenceAdapter&amp;gt;
3. &amp;lt;amqPersistenceAdapter directory=&quot;${activemq.base}/data&quot; maxFileLength=&quot;32mb&quot;/&amp;gt;
4. &amp;lt;/persistenceAdapter&amp;gt;
5. &amp;lt;/broker&amp;gt;

Property Name	Default Value	Comments
directory	activemq-data	the path to the directory to use to store the message store data and log files
useNIO	true	use NIO to write messages to the data logs
syncOnWrite	false	sync every write to disk
maxFileLength	32mb	a hint to set the maximum size of the message data logs
persistentIndex	true	use a persistent index for the message logs. If this is false, an in-memory structure is maintained
maxCheckpointMessageAddSize	4kb	the maximum number of messages to keep in a transaction before automatically committing
cleanupInterval	30000	time (ms) before checking for a discarding/moving message data logs that are no longer used
indexBinSize	1024	default number of bins used by the index. The bigger the bin size -the better the relative performance of the index
indexKeySize	96	the size of the index key - the key is the message id
indexPageSize	16kb	the size of the index page -the bigger the page - the better the write performance of the index
directoryArchive	archive	the path to the directory to use to store discarded data logs
archiveDataLogs	false	if true data logs are moved to the archive directory instead of being deleted
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;232-kaha-persistence&quot;&gt;2．3．2 Kaha Persistence&lt;/h3&gt;

&lt;p&gt;一句话理解：使用data log保存所有消息，每个消息追加到data log上，如果文件中所有的消息都被成功消费，那么这个data log文件会被删除或者归档。&lt;/p&gt;

&lt;p&gt;Kaha Persistence 是一个专门针对消息持久化的解决方案。它对典型的消息使用模式进行了优化。在Kaha中，数据被追加到data logs中。当不再需要log文件中的数据的时候，log文件会被丢弃。以下是其配置的一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;broker brokerName=&quot;broker&quot; persistent=&quot;true&quot; useShutdownHook=&quot;false&quot;&amp;gt;
2. &amp;lt;persistenceAdapter&amp;gt;
3. &amp;lt;kahaPersistenceAdapter directory=&quot;activemq-data&quot; maxDataFileLength=&quot;33554432&quot;/&amp;gt;
4. &amp;lt;/persistenceAdapter&amp;gt;
5. &amp;lt;/broker&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;233-jdbc-persistence&quot;&gt;2．3．3 JDBC Persistence&lt;/h3&gt;

&lt;p&gt;目前支持的数据库有Apache Derby, Axion, DB2, HSQL, Informix, MaxDB, MySQL, Oracle, Postgresql, SQLServer, Sybase。 如果你使用的数据库不被支持，那么可以调整StatementProvider 来保证使用正确的SQL方言（flavour of SQL）。通常绝大多数数据库支持以下&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;adaptor：
• org.activemq.store.jdbc.adapter.BlobJDBCAdapter
• org.activemq.store.jdbc.adapter.BytesJDBCAdapter
• org.activemq.store.jdbc.adapter.DefaultJDBCAdapter
• org.activemq.store.jdbc.adapter.ImageJDBCAdapter
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也可以在配置文件中直接指定JDBC adaptor，例如：
Xml代码&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;jdbcPersistenceAdapter adapterClass=&quot;org.apache.activemq.store.jdbc.adapter.ImageBasedJDBCAdaptor&quot; /&gt;
    &lt;p&gt;以下是其配置的一个例子：&lt;/p&gt;

    &lt;p&gt;Xml代码&lt;/p&gt;
    &lt;ol&gt;
      &lt;li&gt;
        &lt;persistence&gt;
&lt;/persistence&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;jdbcPersistence dataSourceRef=&quot; mysql-ds&quot; /&gt;
      &lt;/li&gt;
      &lt;li&gt;&amp;lt;/persistence&amp;gt;
 4.&lt;/li&gt;
      &lt;li&gt;
        &lt;bean id=&quot;mysql-ds&quot; class=&quot;org.apache.commons.dbcp.BasicDataSource&quot; destroy-method=&quot;close&quot;&gt;
&lt;/bean&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;property name=&quot;driverClassName&quot; value=&quot;com.mysql.jdbc.Driver&quot; /&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;property name=&quot;url&quot; value=&quot;jdbc:mysql://localhost/activemq?relaxAutoCommit=true&quot; /&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;property name=&quot;username&quot; value=&quot;activemq&quot; /&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;property name=&quot;password&quot; value=&quot;activemq&quot; /&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;property name=&quot;poolPreparedStatements&quot; value=&quot;true&quot; /&gt;

        &lt;p&gt;11.&amp;lt;/bean&amp;gt;&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;需要注意的是，如果使用MySQL，那么需要设置relaxAutoCommit 标志为true。&lt;/p&gt;

&lt;h3 id=&quot;234-disable-persistence不需要持久化&quot;&gt;2．3．4 Disable Persistence（不需要持久化）&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;以下是其配置的一个例子：
Xml代码
1. &amp;lt;broker persistent=&quot;false&quot;&amp;gt;
2. &amp;lt;/broker&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;24-安全机制&quot;&gt;2．4 安全机制&lt;/h2&gt;

&lt;p&gt;ActiveMQ支持可插拔的安全机制，用以在不同的provider之间切换。&lt;/p&gt;

&lt;h3 id=&quot;241-simple-authentication-plugin在xml配置用户密码信息&quot;&gt;2．4．1 Simple Authentication Plugin（在xml配置用户密码信息）&lt;/h3&gt;

&lt;p&gt;Simple Authentication Plugin适用于简单的认证需求，或者用于建立测试环境。它允许在XML配置文件中指定用户、用户组和密码等信息。以下是ActiveMQ配置的一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;plugins&amp;gt;
2. ...
3. &amp;lt;simpleAuthenticationPlugin&amp;gt;
4. &amp;lt;users&amp;gt;
5. &amp;lt;authenticationUser username=&quot;system&quot; password=&quot;manager&quot; groups=&quot;users,admins&quot;/&amp;gt;
6. &amp;lt;authenticationUser username=&quot;user&quot; password=&quot;password&quot; groups=&quot;users&quot;/&amp;gt;
7. &amp;lt;authenticationUser username=&quot;guest&quot; password=&quot;password&quot; groups=&quot;guests&quot;/&amp;gt;
8. &amp;lt;/users&amp;gt;
9. &amp;lt;/simpleAuthenticationPlugin&amp;gt;
10.&amp;lt;/plugins&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;242-jaas-authentication-plugin配置loginconfiguserproperties和groupproperties&quot;&gt;2．4．2 JAAS Authentication Plugin（配置login.config，user.properties和group.properties）&lt;/h3&gt;

&lt;p&gt;JAAS Authentication Plugin依赖标准的JAAS机制来实现认证。通常情况下，你需要通过设置java.security.auth.login.config系统属性来配置login modules的配置文件。如果没有指定这个系统属性，那么JAAS Authentication Plugin会缺省使用login.config作为文件名。以下是一个&lt;/p&gt;

&lt;p&gt;login.config文件的例子：
    activemq-domain { 
    org.apache.activemq.jaas.PropertiesLoginModule required debug=true
    org.apache.activemq.jaas.properties.user=”users.properties”
    org.apache.activemq.jaas.properties.group=”groups.properties”; 
    };&lt;/p&gt;

&lt;p&gt;这个login.config文件中设置了两个属性：org.apache.activemq.jaas.properties.user和org.apache.activemq.jaas.properties.group分别用来指向user.properties和group.properties文件。需要注意的是user.properties和group.properties文件存放在跟login.config文件相同的目录里。 以下是ActiveMQ配置的一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;plugins&amp;gt;
2. ...
3. &amp;lt;jaasAuthenticationPlugin configuration=&quot;activemq-domain&quot; /&amp;gt;
4. &amp;lt;/plugins&amp;gt;
5.  基于以上的配置，在JAAS的LoginContext中会使用activemq-domain中配置的PropertiesLoginModule来进行登陆。 ActiveMQ JAAS还支持LDAPLoginModule、CertificateLoginModule、TextFileCertificateLoginModule等login module。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;243-custom-authentication-implementation通过编码的方式配置用户信息&quot;&gt;2．4．3 Custom Authentication Implementation（通过编码的方式配置用户信息）&lt;/h3&gt;

&lt;p&gt;可以通过编码的方式为ActiveMQ增加认证功能。例如编写一个类继承自XBeanBrokerService。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. package com.yourpackage;
2.
3. import java.net.URI;
4. import java.util.HashMap;
5. import java.util.Map;
6.
7. import org.apache.activemq.broker.Broker;
8. import org.apache.activemq.broker.BrokerFactory;
9. import org.apache.activemq.broker.BrokerService;
10.import org.apache.activemq.security.SimpleAuthenticationBroker;
11.import org.apache.activemq.xbean.XBeanBrokerService;
12.
13.public class SimpleAuthBroker extends XBeanBrokerService {
14. //
15. private String user;
16. private String password;
17.
18. @SuppressWarnings(&quot;unchecked&quot;)
19. protected Broker addInterceptors(Broker broker) throws Exception {
20. broker = super.addInterceptors(broker);
21. Map passwords = new HashMap();
22. passwords.put(getUser(), getPassword());
23. broker = new SimpleAuthenticationBroker(broker, passwords, new HashMap());
24. return broker;
25. }
26.
27. public String getUser() {
28. return user;
29. }
30.
31. public void setUser(String user) {
32. this.user = user;
33. }
34.
35. public String getPassword() {
36. return password;
37. }
38.
39. public void setPassword(String password) {
40. this.password = password;
41. }
42.}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;以下是ActiveMQ配置文件的一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;beans&amp;gt;
2. …
3. &amp;lt;auth:SimpleAuthBroker
4. xmlns:auth=&quot;java://com.yourpackage&quot;
5. xmlns=&quot;http://activemq.org/config/1.0&quot; brokerName=&quot;SimpleAuthBroker1&quot; user=&quot;user&quot; password=&quot;password&quot; useJmx=&quot;true&quot;&amp;gt;
6.
7. &amp;lt;transportConnectors&amp;gt;
8. &amp;lt;transportConnector uri=&quot;tcp://localhost:61616&quot;/&amp;gt;
9. &amp;lt;/transportConnectors&amp;gt;
10. &amp;lt;/auth:SimpleAuthBroker&amp;gt;
11. …
12.&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在这个配置文件中增加了一个namespace auth，用于指向之前编写的哪个类。同时为SimpleAuthBroker注入了两个属性值user和password，因此在被SimpleAuthBroker改写的addInterceptors方法里，可以使用这两个属性进行认证了。ActiveMQ提供的SimpleAuthenticationBroker类继承自BrokerFilter（可以简单的看成是Broker的Adaptor），它的构造函数中的两个Map分别是userPasswords和userGroups。 SimpleAuthenticationBroker在 addConnection方法中使用userPasswords进行认证，同时会把userGroups的信息保存到ConnectionContext中 。&lt;/p&gt;

&lt;h3 id=&quot;244-authorization-plugin比较复杂的在xml中配置用户信息&quot;&gt;2．4．4 Authorization Plugin(比较复杂的在xml中配置用户信息)&lt;/h3&gt;

&lt;p&gt;可以通过Authorization Plugin为认证后的用户授权，以下ActiveMQ配置文件的一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;plugins&amp;gt;
2. &amp;lt;jaasAuthenticationPlugin configuration=&quot;activemq-domain&quot;/&amp;gt;
3.
4. &amp;lt;authorizationPlugin&amp;gt;
5. &amp;lt;map&amp;gt;
6. &amp;lt;authorizationMap&amp;gt;
7. &amp;lt;authorizationEntries&amp;gt;
8. &amp;lt;authorizationEntry queue=&quot;&amp;gt;&quot; read=&quot;admins&quot; write=&quot;admins&quot; admin=&quot;admins&quot; /&amp;gt;
9. &amp;lt;authorizationEntry queue=&quot;USERS.&amp;gt;&quot; read=&quot;users&quot; write=&quot;users&quot; admin=&quot;users&quot; /&amp;gt;
10. &amp;lt;authorizationEntry queue=&quot;GUEST.&amp;gt;&quot; read=&quot;guests&quot; write=&quot;guests,users&quot; admin=&quot;guests,users&quot; /&amp;gt;
11.
12. &amp;lt;authorizationEntry topic=&quot;&amp;gt;&quot; read=&quot;admins&quot; write=&quot;admins&quot; admin=&quot;admins&quot; /&amp;gt;
13. &amp;lt;authorizationEntry topic=&quot;USERS.&amp;gt;&quot; read=&quot;users&quot; write=&quot;users&quot; admin=&quot;users&quot; /&amp;gt;
14. &amp;lt;authorizationEntry topic=&quot;GUEST.&amp;gt;&quot; read=&quot;guests&quot; write=&quot;guests,users&quot; admin=&quot;guests,users&quot; /&amp;gt;
15.
16. &amp;lt;authorizationEntry topic=&quot;ActiveMQ.Advisory.&amp;gt;&quot; read=&quot;guests,users&quot; write=&quot;guests,users&quot; admin=&quot;guests,users&quot;/&amp;gt;
17. &amp;lt;/authorizationEntries&amp;gt;
18. &amp;lt;/authorizationMap&amp;gt;
19. &amp;lt;/map&amp;gt;
20. &amp;lt;/authorizationPlugin&amp;gt;
21.&amp;lt;/plugins&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;25-clustering&quot;&gt;2．5 Clustering&lt;/h2&gt;

&lt;p&gt;ActiveMQ从多种不同的方面提供了集群的支持。&lt;/p&gt;

&lt;h3 id=&quot;251-queue-consumer-clusters一个消费者失效消息会发送到另一个消费者上&quot;&gt;2．5．1 Queue consumer clusters（一个消费者失效，消息会发送到另一个消费者上）###&lt;/h3&gt;

&lt;p&gt;ActiveMQ支持订阅同一个queue的consumers上的集群。如果一个consumer失效，那么所有未被确认（unacknowledged）的消息都会被发送到这个queue上其它的consumers。如果某个consumer的处理速度比其它consumers更快，那么这个consumer就会消费更多的消息。 需要注意的是， 笔者发现AcitveMQ5.0版本的Queue consumer clusters存在一个bug： 采用AMQ Message Store，运行一个producer，两个consumer，并采用如下的配置文件：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;beans&amp;gt;
2. &amp;lt;broker xmlns=&quot;http://activemq.org/config/1.0&quot; brokerName=&quot;BugBroker1&quot; useJmx=&quot;true&quot;&amp;gt;
3.
4. &amp;lt;transportConnectors&amp;gt;
5. &amp;lt;transportConnector uri=&quot;tcp://localhost:61616&quot;/&amp;gt;
6. &amp;lt;/transportConnectors&amp;gt;
7.
8. &amp;lt;persistenceAdapter&amp;gt;
9. &amp;lt;amqPersistenceAdapter directory=&quot;activemq-data/BugBroker1&quot; maxFileLength=&quot;32mb&quot;/&amp;gt;
10. &amp;lt;/persistenceAdapter&amp;gt;
11.
12. &amp;lt;/broker&amp;gt;
13.&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么经过一段时间后可能会报出如下错误： 
ERROR [ActiveMQ Transport: tcp:///127.0.0.1:1843 - RecoveryListenerAdapter.java:58 - RecoveryListenerAdapter] 
Message id ID:versus-1837-1203915536609-0:2:1:1:419 could not be recovered from the data store! Apache官方文档说，此bug已经被修正，预定在5.1.0版本上体现。&lt;/p&gt;

&lt;h3 id=&quot;252-broker-clusters一个broker失效跳转到另一个broker&quot;&gt;2．5．2 Broker clusters（一个broker失效，跳转到另一个broker）&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;一句话理解：有一个客户连接到其中一个broker。如果这个broker失效，那么客户会自动重新连接到其它的broker，使用Static Transport或Failover Transport）或动态发现（使用Discovery Transport是用于跳转broker，使用Network of brokers，是用于broker之间存储转发消息&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;一个常见的场景是有多个JMS broker，有一个客户连接到其中一个broker。如果这个broker失效，那么客户会自动重新连接到其它的broker。在ActiveMQ中使用failover:// 协议来实现这个功能。ActiveMQ3.x版本的reliable://协议已经变更为failover://。 如果某个网络上有多个brokers而且客户使用静态发现（使用Static Transport或Failover Transport）或动态发现（使用Discovery Transport），那么客户可以容易地在某个broker失效的情况下切换到其它的brokers。然而，stand alone brokers并不了解其它brokers上的consumers，也就是说如果某个broker上没有consumers，那么这个broker上的消息可能会因得不到处理而积压起来。目前的解决方案是使用Network of brokers，以便在broker之间存储转发消息。ActiveMQ在未来会有更好的特性，用来在客户端处理这个问题。 从ActiveMQ1.1版本起，ActiveMQ支持networks of brokers。它支持分布式的queues和topics。一个broker会相同对待所有的订阅（subscription）：不管他们是来自本地的客户连接，还是来自远程broker，它都会递送有关的消息拷贝到每个订阅。远程broker得到这个消息拷贝后，会依次把它递送到其内部的本地连接上。&lt;/p&gt;

&lt;p&gt;有两种方式配置Network of brokers，一种是使用static transport，如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;broker brokerName=&quot;receiver&quot; persistent=&quot;false&quot; useJmx=&quot;false&quot;&amp;gt;
2. 	&amp;lt;transportConnectors&amp;gt;
3. 		&amp;lt;transportConnector uri=&quot;tcp://localhost:62002&quot;/&amp;gt;
4. 	&amp;lt;/transportConnectors&amp;gt;
5. 	&amp;lt;networkConnectors&amp;gt;
6. 		&amp;lt;networkConnector uri=&quot;static:( tcp://localhost:61616,tcp://remotehost:61616)&quot;/&amp;gt;
7. 	&amp;lt;/networkConnectors&amp;gt;
8. …
9. &amp;lt;/broker&amp;gt;

另外一种是使用multicast discovery，如下：
Xml代码
1. &amp;lt;broker name=&quot;sender&quot; persistent=&quot;false&quot; useJmx=&quot;false&quot;&amp;gt;
2. 	&amp;lt;transportConnectors&amp;gt;
3. 		&amp;lt;transportConnector uri=&quot;tcp://localhost:0&quot; discoveryUri=&quot;multicast://default&quot;/&amp;gt;
4. 	&amp;lt;/transportConnectors&amp;gt;
5. 	&amp;lt;networkConnectors&amp;gt;
6. 		&amp;lt;networkConnector uri=&quot;multicast://default&quot;/&amp;gt;
7. 	&amp;lt;/networkConnectors&amp;gt;
8. ...
9. &amp;lt;/broker&amp;gt;

Network Connector有以下属性：
Property	Default Value	Description
name	bridge	name of the network - for more than one network connector between the same two brokers -use different names
dynamicOnly	false	if true, only forward messages if a consumer is active on the connected broker
decreaseNetworkConsumerPriority	false	decrease the priority for dispatching to a Queue consumer the further away it is (in network hops) from the producer
networkTTL	1	the number of brokers in the network that messages and subscriptions can pass through
conduitSubscriptions	true	multiple consumers subscribing to the same destination are treated as one consumer by the network
excludedDestinations	empty	destinations matching this list won't be forwarded across the network
dynamicallyIncludedDestinations	empty	destinations that match this list will be forwarded across the network n.b. an empty list means all destinations not in the excluded list will be forwarded
staticallyIncludedDestinations	empty	destinations that match will always be passed across the network -even if no consumers have ever registered an interest
duplex	false	if true, a network connection will be used to both produce AND Consume messages. This is useful for hub and spoke scenarios when the hub is behind a firewall etc.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;关于conduitSubscriptions（如果一个broker连接多个消费者，如果为true，那么认为这个broker的所有消费者视为一个）属性，这里稍稍说明一下。设想有两个brokers，分别是brokerA和brokerB，它们之间用forwarding  bridge连接。有一个consumer连接到brokerA并订阅queue：Q.TEST。有两个consumers连接到brokerB，也是订阅queue：Q.TEST。这三个consumers有相同的优先级。然后启动一个producer，它发送了30条消息到brokerA。如果conduitSubscriptions=true，那么brokerA上的consumer会得到15条消息， 另外15条消息会发送给brokerB。此时负载并不均衡，因为此时brokerA将brokerB上的两个consumers视为一个；如果conduitSubscriptions=false，那么每个consumer上都会收到10条消息。以下是关于NetworkConnector属性的一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;networkConnectors&amp;gt;
2. &amp;lt;networkConnector uri=&quot;static://(tcp://localhost:61617)&quot;
3. 		name=&quot;bridge&quot; dynamicOnly=&quot;false&quot; conduitSubscriptions=&quot;true&quot;
4. 		decreaseNetworkConsumerPriority=&quot;false&quot;&amp;gt;
5.   	&amp;lt;excludedDestinations&amp;gt;
6. 		    &amp;lt;queue physicalName=&quot;exclude.test.foo&quot;/&amp;gt;
7. 		    &amp;lt;topic physicalName=&quot;exclude.test.bar&quot;/&amp;gt;
8. 	   &amp;lt;/excludedDestinations&amp;gt;
9. 	   &amp;lt;dynamicallyIncludedDestinations&amp;gt;
10. 		&amp;lt;queue physicalName=&quot;include.test.foo&quot;/&amp;gt;
11. 		&amp;lt;topic physicalName=&quot;include.test.bar&quot;/&amp;gt;
12. 	&amp;lt;/dynamicallyIncludedDestinations&amp;gt;
13. 	&amp;lt;staticallyIncludedDestinations&amp;gt;
14. 		&amp;lt;queue physicalName=&quot;always.include.queue&quot;/&amp;gt;
15. 		&amp;lt;topic physicalName=&quot;always.include.topic&quot;/&amp;gt;
16. 	&amp;lt;/staticallyIncludedDestinations&amp;gt;
17. &amp;lt;/networkConnector&amp;gt;
18.&amp;lt;/networkConnectors&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;253-master-slave&quot;&gt;2．5．3 Master Slave&lt;/h3&gt;

&lt;p&gt;在一个网络内运行多个brokers或者stand alone brokers时存在一个问题，这就是消息在物理上只被一个broker持有，因此当某个broker失效，那么你只能等待直到它重启后，这个broker上的消息才能够被继续发送（如果没有设置持久化，那么在这种情况下，消息将会丢失）。Master Slave 背后的想法是，消息被复制到slave broker，因此即使master broker遇到了像硬件故障之类的错误，你也可以立即切换到slave broker而不丢失任何消息。 Master Slave是目前ActiveMQ推荐的高可靠性和容错的解决方案。以下是几种不同的类型：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Master Slave Type	Requirements	Pros	Cons
Pure Master Slave	None	No central point of failure	Requires manual restart to bring back a failed master and can only support 1 slave
Shared File System Master Slave	A Shared File system such as a SAN	Run as many slaves as required. Automatic recovery of old masters	Requires shared file system
JDBC Master Slave	A Shared database	Run as many slaves as required. Automatic recovery of old masters	Requires a shared database. Also relatively slow as it cannot use the high performance journal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2531-pure-master-slave的工作方式只能有一个slave连接master&quot;&gt;2．5．3．1 Pure Master Slave的工作方式（只能有一个slave连接master）&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;一句话理解：只能有一个slave连接master并复制master的所有消息，只有master和slave都处理完毕，客户端才能得到响应，master失效，客户端连接slave。&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• Slave broker消费master broker上所有的消息状态，例如消息、确认和事务状态等。只要slave broker连接到了master broker，它不会（也不被允许）启动任何network connectors或者transport connectors，所以唯一的目的就是复制master broker的状态。
• Master broker只有在消息成功被复制到slave broker之后才会响应客户。例如，客户的commit请求只有在master broker和slave broker都处理完毕commit请求之后才会结束。
• 当master broker失效的时候，slave broker有两种选择，一种是slave broker启动所有的network connectors和transport connectors，这允许客户端切换到slave broker；另外一种是slave broker停止。这种情况下，slave broker只是复制了master broker的状态。
• 客户应该使用failover transport并且应该首先尝试连接master broker。例如： failover://(tcp://masterhost:61616,tcp://slavehost:61616)?randomize=false 设置randomize为false就可以让客户总是首先尝试连接master broker（slave broker并不会接受任何连接，直到它成为了master broker）。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pure Master Slave具有以下限制：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• 只能有一个slave broker连接到master broker。
• 在因master broker失效而导致slave broker成为master之后，之前的master broker只有在当前的master broker（原slave broker）停止后才能重新生效。
• Master broker失效后而切换到slave broker后，最安全的恢复master broker的方式是人工处理。首先要停止slave broker（这意味着所有的客户也要停止）。然后把slave broker的数据目录中所有的数据拷贝到master broker的数据目录中。然后重启master broker和slave broker。
Master broker不需要特殊的配置。Slave broker需要进行以下配置
Xml代码
1. &amp;lt;broker masterConnectorURI=&quot;tcp://masterhost:62001&quot; shutdownOnMasterFailure=&quot;false&quot;&amp;gt;
2. ...
3. &amp;lt;transportConnectors&amp;gt;
4. &amp;lt;transportConnector uri=&quot;tcp://slavehost:61616&quot;/&amp;gt;
5. &amp;lt;/transportConnectors&amp;gt;
6. &amp;lt;/broker&amp;gt;
其中的masterConnectorURI用于指向master broker，shutdownOnMasterFailure用于指定slave broker在master broker失效的时候是否需要停止。此外，也可以使用如下配置：
Xml代码
1. &amp;lt;broker brokerName=&quot;slave&quot; useJmx=&quot;false&quot; deleteAllMessagesOnStartup=&quot;true&quot; xmlns=&quot;http://activemq.org/config/1.0&quot;&amp;gt;
2. ...
3. &amp;lt;services&amp;gt;
4. &amp;lt;masterConnector remoteURI= &quot;tcp://localhost:62001&quot; userName=&quot;user&quot; password=&quot;password&quot;/&amp;gt;
5. &amp;lt;/services&amp;gt;
6. &amp;lt;/broker&amp;gt;
需要注意的是，笔者认为ActiveMQ5.0版本的Pure Master Slave仍然不够稳定。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2532-shared-file-system-master-slave&quot;&gt;2．5．3．2 Shared File System Master Slave&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;一句话理解：得到共享文件的排他锁的broker就是master&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;如果你使用SAN或者共享文件系统，那么你可以使用Shared File System Master Slave。基本上，你可以运行多个broker，这些broker共享数据目录。当第一个broker得到文件上的排他锁之后，其它的broker便会在循环中等待获得这把锁。客户端使用failover transport来连接到可用的broker。当master broker失效的时候会释放这把锁，这时候其中一个slave broker会得到这把锁从而成为master broker。以下是ActiveMQ配置的一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;broker useJmx=&quot;false&quot; xmlns=&quot;http://activemq.org/config/1.0&quot;&amp;gt;
2. &amp;lt;persistenceAdapter&amp;gt;
3. &amp;lt;journaledJDBC dataDirectory=&quot;/sharedFileSystem/broker&quot;/&amp;gt;
4. &amp;lt;/persistenceAdapter&amp;gt;
5. …
6. &amp;lt;/broker&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2533-jdbc-master-slave&quot;&gt;2．5．3．3 JDBC Master Slave&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;一句话理解：得到数据库的实例的broker就是master&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;JDBC Master Slave工作原理跟Shared File System Master Slave类似，只是采用了数据库作为持久化存储。以下是ActiveMQ配置的一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;beans&amp;gt;
2. &amp;lt;broker xmlns=&quot;http://activemq.org/config/1.0&quot; brokerName=&quot;JdbcMasterBroker&quot;&amp;gt;
3. ...
4. &amp;lt;persistenceAdapter&amp;gt;
5. &amp;lt;jdbcPersistenceAdapter dataSource=&quot;#mysql-ds&quot;/&amp;gt;
6. &amp;lt;/persistenceAdapter&amp;gt;
7.
8. &amp;lt;/broker&amp;gt;
9.
10. &amp;lt;bean id=&quot;mysql-ds&quot; class=&quot;org.apache.commons.dbcp.BasicDataSource&quot; destroy-method=&quot;close&quot;&amp;gt;
11. &amp;lt;property name=&quot;driverClassName&quot; value=&quot;com.mysql.jdbc.Driver&quot;/&amp;gt;
12. &amp;lt;property name=&quot;url&quot; value=&quot;jdbc:mysql://localhost:3306/test?relaxAutoCommit=true&quot;/&amp;gt;
13. &amp;lt;property name=&quot;username&quot; value=&quot;username&quot;/&amp;gt;
14. &amp;lt;property name=&quot;password&quot; value=&quot;passward&quot;/&amp;gt;
15. &amp;lt;property name=&quot;poolPreparedStatements&quot; value=&quot;true&quot;/&amp;gt;
16. &amp;lt;/bean&amp;gt;
17.&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;需要注意的是，如果你使用MySQL数据库，需要首先执行以下三条语句：（Apache官方文档说，此bug已经被修正，预定在5.1.0版本上体现）&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Sql代码
1. ALTER TABLE activemq_acks ENGINE = InnoDB;
2. ALTER TABLE activemq_lock ENGINE = InnoDB;
3. ALTER TABLE activemq_msgs ENGINE = InnoDB;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;26-features&quot;&gt;2．6 Features&lt;/h2&gt;

&lt;p&gt;ActiveMQ包含了很多功能强大的特性，下面简要介绍其中的几个。&lt;/p&gt;

&lt;h3 id=&quot;261-exclusive-consumer&quot;&gt;2．6．1 Exclusive Consumer&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;一句话理解：Broker会从多个consumers中挑选一个consumer来处理queue中所有的消息，从而保证了消息的有序处理&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Queue中的消息是按照顺序被分发到consumers的。然而，当你有多个consumers同时从相同的queue中提取消息时，你将失去这个保证。因为这些消息是被多个线程并发的处理。有的时候，保证消息按照顺序处理是很重要的。例如，你可能不希望在插入订单操作结束之前执行更新这个订单的操作。 ActiveMQ从4.x版本起开始支持Exclusive Consumer （或者说Exclusive Queues）。 Broker会从多个consumers中挑选一个consumer来处理queue中所有的消息，从而保证了消息的有序处理。如果这个consumer失效，那么broker会自动切换到其它的consumer。 可以通过Destination Options 来创建一个Exclusive Consumer，如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. queue = new ActiveMQQueue(&quot;TEST.QUEUE?consumer.exclusive=true&quot;);
2. consumer = session.createConsumer(queue);
顺便说一下，可以给consumer设置优先级，以便针对网络情况（如network hops）进行优化，如下：
Java代码
1. queue = new ActiveMQQueue(&quot;TEST.QUEUE?consumer.exclusive=true &amp;amp;consumer.priority=10&quot;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;262-message-groups&quot;&gt;2．6．2 Message Groups&lt;/h3&gt;

&lt;p&gt;一&lt;strong&gt;句话理解：保证所有具有相同JMSXGroupID 的消息会被分发到相同的consumer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;用Apache官方文档的话说，Message Groups rock！它是Exclusive Consumer功能的增强。逻辑上，Message Groups 可以看成是一种并发的Exclusive Consumer。跟所有的消息都由唯一的consumer处理不同，JMS 消息属性JMSXGroupID 被用来区分message group。Message Groups特性保证所有具有相同JMSXGroupID 的消息会被分发到相同的consumer（只要这个consumer保持active）。另外一方面，Message Groups特性也是一种负载均衡的机制。 在一个消息被分发到consumer之前，broker首先检查消息JMSXGroupID属性。如果存在，那么broker 会检查是否有某个consumer拥有这个message group。如果没有，那么broker会选择一个consumer，并将它关联到这个message group。此后，这个consumer会接收这个message group的所有消息，直到：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• Consumer被关闭。
• Message group被关闭。通过发送一个消息，并设置这个消息的JMSXGroupSeq为0。
从4.1版本开始，ActiveMQ支持一个布尔字段JMSXGroupFirstForConsumer 。当某个message group的第一个消息被发送到consumer的时候，这个字段被设置。如果客户使用failover transport连接到broker。在由于网络问题等造成客户重新连接到broker的时候，相同message group的消息可能会被分发到不同与之前的consumer，因此JMSXGroupFirstForConsumer字段也会被重新设置。
以下是使用message groups的例子：
Java代码
1. Mesasge message = session.createTextMessage(&quot;&amp;lt;foo&amp;gt;hey&amp;lt;/foo&amp;gt;&quot;);
2. message.setStringProperty(&quot;JMSXGroupID&quot;, &quot;IBM_NASDAQ_20/4/05&quot;);
3. ...
4. producer.send(message);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;263-jms-selectors&quot;&gt;2．6．3 JMS Selectors&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;一句话理解：consumer = session.createConsumer(destination, “JMSType = ‘car’ AND weight &amp;gt; 2500”);消费者筛选消费符合条件的消息。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;JMS Selectors用于在订阅中，基于消息属性对消息进行过滤。JMS Selectors由SQL92语法定义。以下是个Selectors的例子：
Java代码
consumer = session.createConsumer(destination, “JMSType = ‘car’ AND weight &amp;gt; 2500”);
在JMS Selectors表达式中，可以使用IN、NOT IN、LIKE等，例如： LIKE ‘12%3’ （’123’ true，’12993’ true，’1234’ false） LIKE ‘l_se’ （’lose’ true，’loose’ false） LIKE ‘_%’ ESCAPE ‘' （’_foo’ true，’foo’ false） 需要注意的是，JMS Selectors表达式中的日期和时间需要使用标准的long型毫秒值。另外表达式中的属性不会自动进行类型转换，例如：
Java代码
myMessage.setStringProperty(“NumberOfOrders”, “2”);
“NumberOfOrders &amp;gt; 1” 求值结果是false。关于JMS Selectors的详细文档请参考javax.jms.Message的javadoc。 上一小节介绍的Message Groups虽然可以保证具有相同message group的消息被唯一的consumer顺序处理，但是却不能确定被哪个consumer处理。在某些情况下，Message Groups可以和JMS Selector一起工作，例如： 设想有三个consumers分别是A、B和C。你可以在producer中为消息设置三个message groups分别是”A”、”B”和”C”。然后令consumer A使用”JMXGroupID = ‘A’“作为selector。B和C也同理。这样就可以保证message group A的消息只被consumer A处理。需要注意的是，这种做法有以下缺点：
• producer必须知道当前正在运行的consumers，也就是说producer和consumer被耦合到一起。
• 如果某个consumer失效，那么应该被这个consumer消费的消息将会一直被积压在broker上。&lt;/p&gt;

&lt;h3 id=&quot;264-pending-message-limit-strategy待发送信息的限制策略&quot;&gt;2．6．4 Pending Message Limit Strategy（待发送信息的限制策略）&lt;/h3&gt;

&lt;p&gt;首先简要介绍一下prefetch机制。ActiveMQ通过prefetch机制来提高性能，这意味这客户端的内存里可能会缓存一定数量的消息。缓存消息的数量由prefetch limit来控制。当某个consumer的prefetch buffer已经达到上限，那么broker不会再向consumer分发消息，直到consumer向broker发送消息的确认。可以通过在ActiveMQConnectionFactory或者ActiveMQConnection上设置&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ActiveMQPrefetchPolicy对象来配置prefetch policy。也可以通过connection options或者destination options来配置。例如：
tcp://localhost:61616?jms.prefetchPolicy.all=50 
tcp://localhost:61616?jms.prefetchPolicy.queuePrefetch=1 
queue = new ActiveMQQueue(&quot;TEST.QUEUE?consumer.prefetchSize=10&quot;); prefetch size的缺省值如下：
• persistent queues (default value: 1000)
• non-persistent queues (default value: 1000)
• persistent topics (default value: 100)
• non-persistent topics (default value: Short.MAX_VALUE -1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;慢消费者会在非持久的topics上导致问题：一旦消息积压起来，会导致broker把大量消息保存在内存中，broker也会因此而变慢。未来ActiveMQ可能会实现磁盘缓存，但是这也还是会存在性能问题。目前ActiveMQ使用Pending Message Limit Strategy来解决这个问题。除了prefetch buffer之外，你还要配置缓存消息的上限，超过这个上限后，新消息到来时会丢弃旧消息。通过在配置文件的destination map中配置PendingMessageLimitStrategy，可以为不用的topic namespace配置不同的策略。目前有以下两种：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• ConstantPendingMessageLimitStrategy。这个策略使用常量限制。 例如：&amp;lt;constantPendingMessageLimitStrategy limit=&quot;50&quot;/&amp;gt;
• PrefetchRatePendingMessageLimitStrategy。这个策略使用prefetch size的倍数限制。 例如：&amp;lt;prefetchRatePendingMessageLimitStrategy multiplier=&quot;2.5&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在以上两种方式中，如果设置0意味着除了prefetch之外不再缓存消息；如果设置-1意味着禁止丢弃消息。 此外，你还可以配置消息的丢弃策略，目前有以下两种：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• oldestMessageEvictionStrategy。这个策略丢弃最旧的消息。
• oldestMessageWithLowestPriorityEvictionStrategy。这个策略丢弃最旧的，而且具有最低优先级的消息。
以下是个ActiveMQ配置文件的例子：
Xml代码
1. &amp;lt;broker persistent=&quot;false&quot; brokerName=&quot;${brokername}&quot; xmlns=&quot;http://activemq.org/config/1.0&quot;&amp;gt;
2. 	&amp;lt;destinationPolicy&amp;gt;
3. 		&amp;lt;policyMap&amp;gt;
4. 			&amp;lt;policyEntries&amp;gt;
5. 				&amp;lt;policyEntry topic=&quot;PRICES.&amp;gt;&quot;&amp;gt;
6. 
7. 					&amp;lt;subscriptionRecoveryPolicy&amp;gt;
8. 						&amp;lt;timedSubscriptionRecoveryPolicy recoverDuration=&quot;10000&quot; /&amp;gt;
9. 					&amp;lt;/subscriptionRecoveryPolicy&amp;gt;
10.
11. 
12. 					&amp;lt;pendingMessageLimitStrategy&amp;gt;
13. 						&amp;lt;constantPendingMessageLimitStrategy limit=&quot;10&quot;/&amp;gt;
14. 					&amp;lt;/pendingMessageLimitStrategy&amp;gt;
15. 				&amp;lt;/policyEntry&amp;gt;
16. 			&amp;lt;/policyEntries&amp;gt;
17. 		&amp;lt;/policyMap&amp;gt;
18. 	&amp;lt;/destinationPolicy&amp;gt;
19. ...
20.&amp;lt;/broker&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;265-composite-destinations&quot;&gt;2．6．5 Composite Destinations&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;一句话理解：允许用一个虚拟的destination 代表多个destinations，向虚拟destination发送消息就是向多个destination发送消息。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;从1.1版本起, ActiveMQ支持composite destinations。它允许用一个虚拟的destination 代表多个destinations。例如你可以通过composite destinations在一个操作中同时向12个queue发送消息。在composite destinations中，多个destination之间采用”,”分割。例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. Queue queue = new ActiveMQQueue(&quot;FOO.A,FOO.B,FOO.C&quot;);
如果你希望使用不同类型的destination，那么需要加上前缀如queue:// 或topic://，例如：
Java代码
1. Queue queue = new ActiveMQQueue(&quot;FOO.A,topic://NOTIFY.FOO.A&quot;);
以下是ActiveMQ配置文件进行配置的一个例子：
Xml代码
1. &amp;lt;destinationInterceptors&amp;gt;
2. 	&amp;lt;virtualDestinationInterceptor&amp;gt;
3. 		&amp;lt;virtualDestinations&amp;gt;
4. 			&amp;lt;compositeQueue name=&quot;MY.QUEUE&quot;&amp;gt;
5. 				&amp;lt;forwardTo&amp;gt;
6. 					&amp;lt;queue physicalName=&quot;FOO&quot; /&amp;gt;
7. 					&amp;lt;topic physicalName=&quot;BAR&quot; /&amp;gt;
8. 				&amp;lt;/forwardTo&amp;gt;
9. 			&amp;lt;/compositeQueue&amp;gt;
10. 		&amp;lt;/virtualDestinations&amp;gt;
11. 	&amp;lt;/virtualDestinationInterceptor&amp;gt;
12.&amp;lt;/destinationInterceptors&amp;gt;
可以在转发前，先通过JMS Selector判断一个消息是否需要转发，例如：
Xml代码
1. &amp;lt;destinationInterceptors&amp;gt;
2. 	&amp;lt;virtualDestinationInterceptor&amp;gt;
3. 		&amp;lt;virtualDestinations&amp;gt;
4. 			&amp;lt;compositeQueue name=&quot;MY.QUEUE&quot;&amp;gt;
5. 				&amp;lt;forwardTo&amp;gt;
6. 					&amp;lt;filteredDestination selector=&quot;odd = 'yes'&quot; queue=&quot;FOO&quot;/&amp;gt;
7. 					&amp;lt;filteredDestination selector=&quot;i = 5&quot; topic=&quot;BAR&quot;/&amp;gt;
8. 				&amp;lt;/forwardTo&amp;gt;
9. 			&amp;lt;/compositeQueue&amp;gt;
10. 		&amp;lt;/virtualDestinations&amp;gt;
11. 	&amp;lt;/virtualDestinationInterceptor&amp;gt;
12.&amp;lt;/destinationInterceptors&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;###2．6．6 Mirrored Queues ###&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;一句话理解： Broker会把发送到某个queue的所有消息转发到一个名称类似的topic，因此监控程序可以订阅这个mirrored queue topic。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;每个queue中的消息只能被一个consumer消费。然而，有时候你可能希望能够监视生产者和消费者之间的消息流。你可以通过使用Virtual Destinations 来建立一个virtual queue 来把消息转发到多个queues中。但是 为系统中每个queue都进行如此的配置可能会很麻烦。 ActiveMQ支持Mirrored Queues。Broker会把发送到某个queue的所有消息转发到一个名称类似的topic，因此监控程序可以订阅这个mirrored queue topic。为了启用Mirrored Queues，首先要将BrokerService的useMirroredQueues属性设置成true，然后可以通过destinationInterceptors设置其它属性，如mirror topic的前缀，缺省是”VirtualTopic.Mirror.”。以下是ActiveMQ配置文件的一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;broker xmlns=&quot;http://activemq.org/config/1.0&quot; brokerName=&quot;MirroredQueuesBroker1&quot; useMirroredQueues=&quot;true&quot;&amp;gt;
2.
3. &amp;lt;transportConnectors&amp;gt;
4. &amp;lt;transportConnector uri=&quot;tcp://localhost:61616&quot;/&amp;gt;
5. &amp;lt;/transportConnectors&amp;gt;
6.
7. &amp;lt;destinationInterceptors&amp;gt;
8. &amp;lt;mirroredQueue copyMessage = &quot;true&quot; prefix=&quot;Mirror.Topic&quot;/&amp;gt;
9. &amp;lt;/destinationInterceptors&amp;gt;
10. ...
11.&amp;lt;/broker&amp;gt;
假如某个producer向名为Foo.Bar的queue中发送消息，那么你可以通过订阅名为Mirror.Topic.Foo.Bar的topic来获得发送到Foo.Bar中的所有消息。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;267-wildcards&quot;&gt;2．6．7 Wildcards&lt;/h3&gt;

&lt;p&gt;Wildcards用来支持联合的名字分层体系（federated name hierarchies）。它不是JMS规范的一部分，而是ActiveMQ的扩展。ActiveMQ支持以下三种wildcards：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• &quot;.&quot; 用于作为路径上名字间的分隔符。
• &quot;*&quot; 用于匹配路径上的任何名字。
• &quot;&amp;gt;&quot; 用于递归地匹配任何以这个名字开始的destination。
作为一种组织事件和订阅感兴趣那部分信息的一种方法，这个概念在金融市场领域已经流行了一段时间了。设想你有以下两个destination：
• PRICE.STOCK.NASDAQ.IBM （IBM在NASDAQ的股价）
• PRICE.STOCK.NYSE.SUNW （SUN在纽约证券交易所的股价）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;订阅者可以明确地指定destination的名字来订阅消息，或者它也可以使用wildcards来定义一个分层的模式来匹配它希望订阅的destination。例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Subscription				Meaning
PRICE.&amp;gt;					Any price for any product on any exchange
PRICE.STOCK.&amp;gt;			Any price for a stock on any exchange
PRICE.STOCK.NASDAQ.*	Any stock price on NASDAQ
PRICE.STOCK.*.IBM		Any IBM stock price on any exchange
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;268-async-sends&quot;&gt;2．6．8 Async Sends&lt;/h3&gt;

&lt;p&gt;ActiveMQ支持以同步（sync）方式或者异步（async）方式向broker发送消息。 使用何种方式对send方法的延迟有巨大的影响。对于生产者来说，既然延迟是决定吞吐量的重要因素，那么使用异步发送方式会极大地提高系统的性能。 ActiveMQ缺省使用异步传输方式。但是按照JMS规范，当在事务外发送持久化消息的时候，ActiveMQ会强制使用同步发送方式。在这种情况下，每一次发送都是同步的，而且阻塞到收到broker的应答。这个应答保证了broker已经成功地将消息持久化，而且不会丢失。但是这样作也严重地影响了性能。 如果你的系统可以容忍少量的消息丢失，那么可以在事务外发送持久消息的时候，选择使用异步方式。以下是几种不同的配置方式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. cf = new ActiveMQConnectionFactory(&quot;tcp://locahost:61616?jms.useAsyncSend=true&quot;);
2. ((ActiveMQConnectionFactory)connectionFactory).setUseAsyncSend(true);
3. ((ActiveMQConnection)connection).setUseAsyncSend(true);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;269-dispatch-policies&quot;&gt;2．6．9 Dispatch Policies&lt;/h3&gt;

&lt;h4 id=&quot;2691-round-robin-dispatch-policy轮询消费者转发消息平均分发消息&quot;&gt;2．6．9．1 Round Robin Dispatch Policy(轮询消费者转发消息，平均分发消息)&lt;/h4&gt;

&lt;p&gt;在2．6．4小节介绍过ActiveMQ的prefetch机制，ActiveMQ的缺省参数是针对处理大量消息时的高性能和高吞吐量而设置的。所以缺省的prefetch参数比较大，而且缺省的dispatch policies会尝试尽可能快的填满prefetch缓冲。然而在有些情况下，例如只有少量的消息而且单个消息的处理时间比较长，那么在缺省的prefetch和dispatch policies下，这些少量的消息总是倾向于被分发到个别的consumer上。这样就会因为负载的不均衡分配而导致处理时间的增加。 Round robin dispatch policy会尝试平均分发消息，以下是ActiveMQ配置文件的一个例子：
Xml代码&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. &amp;lt;destinationPolicy&amp;gt;
2. &amp;lt;policyMap&amp;gt;
3. &amp;lt;policyEntries&amp;gt;
4. &amp;lt;policyEntry topic=&quot;FOO.&amp;gt;&quot;&amp;gt;
5. &amp;lt;dispatchPolicy&amp;gt;
6. &amp;lt;roundRobinDispatchPolicy /&amp;gt;
7. &amp;lt;/dispatchPolicy&amp;gt;
8. &amp;lt;/policyEntry&amp;gt;
9. &amp;lt;/policyEntries&amp;gt;
10. &amp;lt;/policyMap&amp;gt;
11.&amp;lt;/destinationPolicy&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2692-strict-order-dispatch-policy保证不同的topic-consumer以相同的顺序接收消息&quot;&gt;2．6．9．2 Strict Order Dispatch Policy（保证不同的topic consumer以相同的顺序接收消息）&lt;/h4&gt;

&lt;p&gt;有时候需要保证不同的topic consumer以相同的顺序接收消息。通常ActiveMQ会保证topic consumer以相同的顺序接收来自同一个producer的消息。然而，由于多线程和异步处理，不同的topic consumer可能会以不同的顺序接收来自不同producer的消息。例如有两个producer，分别是P和Q。差不多是同一时间内，P发送了P1、P2和P3三个消息；Q发送了Q1和Q2两个消息。两个不同的consumer可能会以以下顺序接收到消息：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;consumer1: P1 P2 Q1 P3 Q2 
consumer2: P1 Q1 Q2 P2 P3 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Strict order dispatch policy 会保证每个topic consumer会以相同的顺序接收消息，代价是性能上的损失。以下是采用了strict order dispatch policy后，两个不同的consumer可能以以下的顺序接收消息：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;consumer1: P1 P2 Q1 P3 Q2 
consumer2: P1 P2 Q1 P3 Q2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;以下是ActiveMQ配置文件的一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;destinationPolicy&amp;gt;
2. &amp;lt;policyMap&amp;gt;
3. &amp;lt;policyEntries&amp;gt;
4. &amp;lt;policyEntry topic=&quot;&quot;FOO.&amp;gt;&quot;&amp;gt;
5. &amp;lt;dispatchPolicy&amp;gt;
6. &amp;lt;strictOrderDispatchPolicy /&amp;gt;
7. &amp;lt;/dispatchPolicy&amp;gt;
8. &amp;lt;/policyEntry&amp;gt;
9. &amp;lt;/policyEntries&amp;gt;
10. &amp;lt;/policyMap&amp;gt;
11.&amp;lt;/destinationPolicy&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2610-message-cursors&quot;&gt;2．6．10 Message Cursors&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;一句话理解：对待转发消息的引用方式&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;当producer发送的持久化消息到达broker之后，broker首先会把它保存在持久存储中。接下来，如果发现当前有活跃的consumer，而且这个consumer消费消息的速度能跟上producer生产消息的速度，那么ActiveMQ会直接把消息传递给broker内部跟这个consumer关联的dispatch queue；如果当前没有活跃的consumer或者consumer消费消息的速度跟不上producer生产消息的速度，那么ActiveMQ会使用Pending Message Cursors保存对消息的引用。在需要的时候，Pending Message Cursors把消息引用传递给broker内部跟这个consumer关联的dispatch queue。以下是两种Pending Message Cursors：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• VM Cursor。在内存中保存消息的引用。
• File Cursor。首先在内存中保存消息的引用，如果内存使用量达到上限，那么会把消息引用保存到临时文件中。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在缺省情况下，ActiveMQ 5.0根据使用的Message Store来决定使用何种类型的Message Cursors，但是你可以根据destination来配置Message Cursors。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;对于topic&lt;/strong&gt;，可以使用的pendingSubscriberPolicy 有vmCursor和fileCursor。可以使用的PendingDurableSubscriberMessageStoragePolicy有vmDurableCursor 和 fileDurableSubscriberCursor。以下是ActiveMQ配置文件的一个例子：
Xml代码&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. &amp;lt;destinationPolicy&amp;gt;
2. &amp;lt;policyMap&amp;gt;
3. &amp;lt;policyEntries&amp;gt;
4. &amp;lt;policyEntry topic=&quot;org.apache.&amp;gt;&quot;&amp;gt;
5. &amp;lt;pendingSubscriberPolicy&amp;gt;
6. &amp;lt;vmCursor /&amp;gt;
7. &amp;lt;/pendingSubscriberPolicy&amp;gt;
8. &amp;lt;PendingDurableSubscriberMessageStoragePolicy&amp;gt;
9. &amp;lt;vmDurableCursor/&amp;gt;
10. &amp;lt;/PendingDurableSubscriberMessageStoragePolicy&amp;gt;
11. &amp;lt;/policyEntry&amp;gt;
12. &amp;lt;/policyEntries&amp;gt;
13. &amp;lt;/policyMap&amp;gt;
14.&amp;lt;/destinationPolicy&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;对于queue&lt;/strong&gt;，可以使用的pendingQueuePolicy有vmQueueCursor 和 fileQueueCursor。以下是ActiveMQ配置文件的一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Xml代码
1. &amp;lt;destinationPolicy&amp;gt;
2. &amp;lt;policyMap&amp;gt;
3. &amp;lt;policyEntries&amp;gt;
4. &amp;lt;policyEntry queue=&quot;org.apache.&amp;gt;&quot;&amp;gt;
5. &amp;lt;pendingQueuePolicy&amp;gt;
6. &amp;lt;vmQueueCursor /&amp;gt;
7. &amp;lt;/pendingQueuePolicy&amp;gt;
8. &amp;lt;/policyEntry&amp;gt;
9. &amp;lt;/policyEntries&amp;gt;
10. &amp;lt;/policyMap&amp;gt;
11.&amp;lt;/destinationPolicy&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2611-optimized-acknowledgement批量确认&quot;&gt;2．6．11 Optimized Acknowledgement（批量确认）###&lt;/h3&gt;

&lt;p&gt;ActiveMQ缺省支持批量确认消息。由于批量确认会提高性能，因此这是缺省的确认方式。如果希望在应用程序中禁止经过优化的确认方式，那么可以采用如下方法：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. cf = new ActiveMQConnectionFactory (&quot;tcp://locahost:61616?jms.optimizeAcknowledge=false&quot;);
2. ((ActiveMQConnectionFactory)connectionFactory).setOptimizeAcknowledge(false);
3. ((ActiveMQConnection)connection).setOptimizeAcknowledge(false);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;###2．6．12 Producer Flow Control（生产者流量控制） ###&lt;/p&gt;

&lt;p&gt;同步发送消息的producer会自动使用producer flow control ；对于异步发送消息的producer，要使用producer flow control，你先要为connection配置一个ProducerWindowSize参数，如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. ((ActiveMQConnectionFactory)cf).setProducerWindowSize(1024000);
ProducerWindowSize是producer在发送消息的过程中，收到broker对于之前发送消息的确认之前， 能够发送消息的最大字节数。你也可以禁用producer flow control，以下是ActiveMQ配置文件的一个例子：
Java代码
1. &amp;lt;destinationPolicy&amp;gt;
2. &amp;lt;policyMap&amp;gt;
3. &amp;lt;policyEntries&amp;gt;
4. &amp;lt;policyEntry topic=&quot;FOO.&amp;gt;&quot; producerFlowControl=&quot;false&quot;&amp;gt;
5. &amp;lt;dispatchPolicy&amp;gt;
6. &amp;lt;strictOrderDispatchPolicy/&amp;gt;
7. &amp;lt;/dispatchPolicy&amp;gt;
8. &amp;lt;/policyEntry&amp;gt;
9. &amp;lt;/policyEntries&amp;gt;
10. &amp;lt;/policyMap&amp;gt;
11.&amp;lt;/destinationPolicy&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2613-message-transformation&quot;&gt;2．6．13 Message Transformation&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;一句话理解：生产时先转化在发送，消费时先转化在消费&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;有时候需要在JMS Provider内部进行message的转换。从4.2版本起，ActiveMQ 提供了一个MessageTransformer 接口用于进行消息转换，如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Java代码
1. public interface MessageTransformer {
2. Message producerTransform(Session session, MessageProducer producer, Message message) throws JMSException;
3. Message consumerTransform(Session session, MessageConsumer consumer, Message message)throws JMSException;
4. }
5.  通过在以下对象上通过调用setTransformer方法来设置MessageTransformer：

• ActiveMQConnectionFactory
• ActiveMQConnection
• ActiveMQSession
• ActiveMQMessageConsumer
• ActiveMQMessageProducer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;MessageTransformer接口支持：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• 在消息被发送到JMS Provider的消息总线前进行转换。通过producerTransform方法。
• 在消息到达消息总线后，但是在consumer接收到消息前进行转换。通过consumerTransform方法。

以下是个简单的例子：
Java代码
1. public class SimpleMessage implements Serializable {
2. //
3. private static final long serialVersionUID = 2251041841871975105L;
4.
5. //
6. private String id;
7. private String text;
8.
9. public String getId() {
10. return id;
11. }
12. public void setId(String id) {
13. this.id = id;
14. }
15. public String getText() {
16. return text;
17. }
18. public void setText(String text) {
19. this.text = text;
20. }
21.}
在producer内发送ObjectMessage，如下：
Java代码
1. SimpleMessage sm = new SimpleMessage();
2. sm.setId(&quot;1&quot;);
3. sm.setText(&quot;this is a sample message&quot;);
4. ObjectMessage message = session.createObjectMessage();
5. message.setObject(sm);
6. producer.send(message);
在consumer的session上设置一个MessageTransformer用于将ObjectMessage转换成TextMessage，如下：
Java代码
1. ((ActiveMQSession)session).setTransformer(new MessageTransformer() {
2. public Message consumerTransform(Session session, MessageConsumer consumer, Message message) throws JMSException {
3. ObjectMessage om = (ObjectMessage)message;
4. XStream xstream = new XStream();
5. xstream.alias(&quot;simple message&quot;, SimpleMessage.class);
6. String xml = xstream.toXML(om.getObject());
7. return session.createTextMessage(xml);
8. }
9.
10.public Message producerTransform(Session session, MessageProducer consumer, Message message) throws JMSException {
11. return null;
12.}
13.});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Qing Shuang</name></author><category term="ActiveMessenger" /><summary type="html">1 JMS</summary></entry></feed>