<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
    <channel>
                    <title>qf二连</title>
            <link>https://seansun.xyz</link>
                <description>生命在于休息</description>
        <language>zh-CN</language>
        <generator>Halo 1.4.2</generator>
                <item>
                    <title>
                        <![CDATA[ftp 简介]]>
                    </title>
                    <link>https://seansun.xyz/archives/f-t-p--jian-jie</link>
                    <description>
                            <![CDATA[<h1 id="ftp-是什么">FTP 是什么</h1><p>FTP 是文件传输协议(File Transfer Protocol)的简称，用来传输文件，基于TCP/IP，是可靠的文件传输协议，但是不安全，所以为安全考虑会和SSL(TLS)一起使用。</p><blockquote><p>2021年 google chrome(version 88.0) 和 firefox(version 88.0)已经放弃对ftp的支持。<br />明明已经过时了，我也不知道我为什么要写-_-||</p></blockquote><h1 id="通信模式">通信模式</h1><h2 id="主动模式">主动模式</h2><ol><li>客户端打开一个随机的端口（端口号大于1024，在这里，我们称它为x），同时一个FTP进程连接至服务器的21号命令端口。此时，该tcp连接的来源地端口为客户端指定的随机端口x，目的地端口（远程端口）为服务器上的21号端口。</li><li>客户端开始监听端口（x+1），同时向服务器发送一个端口命令（通过服务器的21号命令端口），此命令告诉服务器客户端正在监听的端口号并且已准备好从此端口接收数据。这个端口就是我们所知的数据端口。</li><li>服务器打开20号源端口并且创建和客户端数据端口的连接。此时，来源地的端口为20，远程数据(目的地)端口为（x+1）。</li><li>客户端通过本地的数据端口创建一个和服务器20号端口的连接，然后向服务器发送一个应答，告诉服务器它已经创建好了一个连接。</li></ol><blockquote><p>主动模式需要从服务器建立到本地客户端的连接，但是现有防火墙的安全模式常常是准出不准进的，所以在客户端有防火墙的情况下，主动模式并不友好。<br />NAT也是同样的道理，虽然可以通过端口转发克服。</p></blockquote><h2 id="被动模式">被动模式</h2><ol><li>客户端还是访问服务器的21号端口，并且告诉服务器是PASV(被动模式)</li><li>服务器返回相应被动监听的端口信息如端口是2048</li><li>客户端连接服务器端口2048进行数据连接</li></ol><h1 id="数据类型">数据类型</h1><p>维基百科上说了4种数据类型，但是实际上常用的就是BinaryMode。</p><blockquote><p>还记得有一次使用ftp客户端传文件(老业务了)，结果对方一直说数据不完整，后面发现 java 客户端默认模式是ASCII模式(大概就是英文文本模式),会在传输前把数据转换成&quot;8-bit ASCII&quot;<br />看到协议里面有写数据类型的时候才恍然大悟。</p></blockquote><h1 id="安装">安装</h1><h2 id="centos">centos</h2><pre><code class="language-bash">yum install -y vsftpdsystemctl start vsftpd --now</code></pre>]]>
                    </description>
                    <pubDate>2021-08-14T23:16:36+08:00</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[firewalld 简单介绍]]>
                    </title>
                    <link>https://seansun.xyz/archives/firewalld简单介绍</link>
                    <description>
                            <![CDATA[<h1 id="firewalld-介绍">firewalld 介绍、</h1><p>Firewalld 提供了可被动态管理的防火墙，并且支持以 network/firewall zone 的概念定义网络连接和接口的可信级别。 支持IPv4,IPv6防火墙设置，以太网桥和IP sets.</p><p>它是运行时(runtime)和持久化(permanent)配置分离的。他也提供了接口来给应用和服务直接添加防火墙规则。</p><h2 id="firewalld的优点">firewalld的优点</h2><p>runtime 环境的配置修改会立刻生效，不需要重启服务和守护进程。<br />使用 firewalld D-Bus 接口对于应用、服务和用户来说都非常容易适配防火墙配置。这些接口是完整的，并且firewall-cmd, firewall-config, fireall-applet等防火墙配置工具都在使用。</p><p>因为分离了运行时和持久化的配置，所以可以用运行时配置进行评估实践。运行时配置只在下次服务 reload 或 restart 和系统重启之前生效，之后持久化的配置会被再次加载。<br />运行时的配置，如果被评估为没有问题，也能直接用命令行将运行时配置变为持久化的配置。</p><h2 id="概念">概念</h2><p>zone: interface + rule构成</p><h2 id="调整rule">调整rule</h2><h3 id="调整端口规则">调整端口规则</h3><p>在默认的zone里面放行一个9888的tcp端口</p><pre><code class="language-bash">firewall-cmd --add-port=9888/tcp</code></pre><p>在默认zone里面删除一个9888的tcp端口</p><pre><code class="language-bash">firewall-cmd --remove-port=9888/tcp</code></pre><h3 id="调整服务规则">调整服务规则</h3><p>预先定义好的规则可以在目录 /usr/lib/firewalld/services 下面查找，比如 vnc-server.xml 具体内容如下:</p><pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;&lt;service&gt;  &lt;short&gt;Virtual Network Computing Server (VNC)&lt;/short&gt;  &lt;description&gt;A VNC server provides an external accessible X session. Enable this option if you plan to provide a VNC server with direct access. The access will be possible for displays :0 to :3. If you plan to provide access with SSH, do not open this option and use the via option of the VNC viewer.&lt;/description&gt;  &lt;port protocol=&quot;tcp&quot; port=&quot;5900-5903&quot;/&gt;&lt;/service&gt;</code></pre><p>在一个service文件里面能定义多个端口和协议的组合</p><p>在默认zone里面增加一个协议</p><pre><code class="language-bash">firewall-cmd --add-service=vnc-server</code></pre><p>在默认zone里面删除一个协议</p><pre><code class="language-bash">firewall-cmd --remove-service=vnc-server</code></pre><h3 id="调整-rich-rules">调整 rich-rules</h3><p>在默认zone里面添加一个rich rule</p><pre><code class="language-bash">firewall-cmd --add-rich-rule='rule family=&quot;ipv4&quot; source address=&quot;3.3.3.3/32&quot; destination address=&quot;2.2.2.3/32&quot; service name=&quot;dns&quot; drop'</code></pre><p>在默认zone里面删除一个rich rule</p><pre><code class="language-bash">firewall-cmd --remove-rich-rule='rule family=&quot;ipv4&quot; source address=&quot;3.3.3.3/32&quot; destination address=&quot;2.2.2.3/32&quot; service name=&quot;dns&quot; drop'</code></pre><h2 id="持久化临时runtime规则">持久化临时(runtime)规则</h2><p>firewall-cmd --runtime-to-permanent</p>]]>
                    </description>
                    <pubDate>2021-08-14T23:12:54+08:00</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[selinux简单介绍]]>
                    </title>
                    <link>https://seansun.xyz/archives/selinux简单介绍</link>
                    <description>
                            <![CDATA[<p>[toc]</p><h1 id="selinux简介">SELinux简介</h1><p>安全增强型 Linux（SELinux）是一种采用安全架构的 Linux® 系统，它能够让管理员更好地管控哪些人可以访问系统。它最初是作为 Linux 内核的一系列补丁，由美国国家安全局（NSA）利用 Linux 安全模块（LSM）开发而成。</p><p>SELinux 于 2000 年发布到开源社区，并于 2003 年集成到上游 Linux 内核中。</p><h1 id="配置文件">配置文件</h1><p><code>/etc/sysconfig/selinux</code> 可以在配置文件中配置允许模式、强制模式还是处于禁用状态，以及要加载哪个策略。</p><h1 id="selinux-标签和类型强制访问控制">SELinux 标签和类型强制访问控制</h1><p>类型强制访问控制和标签是 SELinux 中最为重要的两个概念。</p><p>SELinux 可作为标签系统运行，也就是说，系统中的所有文件、进程和端口都具有与之关联的 SELinux 标签。标签可以按照逻辑将目标组合分类。在启动过程中，内核负责管理标签。</p><p>标签的格式为 user:role:type:level（level 为可选项）。User、role 和 level 用于类似 <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/selinux_users_and_administrators_guide/mls">MLS</a> 的更高级的 SELinux 实施中。标签类型对于目标策略而言最为重要。</p><p>SELinux 利用类型强制访问控制来强制执行系统中定义的策略。类型强制访问控制是 SELinux 策略的一部分，它定义了特定类型的进程能否访问标记为特定类型的文件。</p><h1 id="自主访问控制dac与强制访问控制mac">自主访问控制（DAC）与强制访问控制（MAC）</h1><p>传统上，Linux 和 UNIX 系统都采用 DAC。SELinux 是 Linux 采用 MAC 机制的一个示例。</p><p>对于 DAC 而言，文件和进程都有相应的所有者。您可以让用户拥有某个文件，让群组拥有某个文件，或让其他人（可以是其他任何人）拥有某个文件。用户可以更改自己文件的权限。</p><p>根用户对 DAC 系统拥有完全访问控制权。如果您拥有根访问权限，则可以访问其他任何用户的文件，或在系统上执行任何操作。</p><p>但在像 SELinux 这样的 MAC 系统上，访问权限有相应的管理设置策略。即使主目录上的 DAC 设置发生更改，SELinux 策略也会阻止其他用户或进程访问目录，从而保证系统的安全。</p><p>SELinux 策略可以让您针对性设置，并且涵盖大量进程。您可以对 SELinux 进行更改，以限制用户、文件、目录等等之间的访问。</p><h1 id="文件上下文操作">文件上下文操作</h1><p>一般来说MLS可能使用的不会很多，更多的针对文件系统的可能是针对文件系统来打上上下文类型标记，也就是selinux 标签中的第三个字段</p><h2 id="查看指定文件的-selinux-上下文">查看指定文件的 selinux 上下文</h2><p><code>ls -Z</code></p><h2 id="修改指定文件的安全上下文">修改指定文件的安全上下文</h2><p>把 a 文件的安全上下文变成wawawa<br /><code>chcon -t wawawa a</code></p><blockquote><p>如果文件安全上下文配置的是其他类型，则恢复操作会让简单的修改文件安全上下文变成配置的规则中的上下文</p></blockquote><h2 id="查看-selinux-文件安全上下文的生成规则">查看 selinux 文件安全上下文的生成规则</h2><p><code>semanage fcontext -l</code></p><h2 id="添加-selinux-文件安全上下文的生成规则">添加 selinux 文件安全上下文的生成规则</h2><p>增加一条规则把/var/my/web/page目录及下面的所有文件的类型修改为my_type<br /><code>semanage fcontext -a -t my_type '/var/my/web/page(/.*)?'</code></p><blockquote><p>my_type 应该是实际支持的类型</p></blockquote><h2 id="恢复不正确的上下文">恢复不正确的上下文</h2><p><code>restorecon -r /dir/to/restore</code></p><blockquote><p>在根目录下创建 .autorelabel 重启之后会自动恢复不正确的上下文</p></blockquote><h1 id="端口上下文操作">端口上下文操作</h1><h2 id="查询所有端口上下文标签">查询所有端口上下文标签</h2><p><code>semanage port -l</code></p><h2 id="添加端口">添加端口</h2><p>给 12345/tcp 增加 http_port_t 类型的安全上下文标签<br /><code>semanage port -a -t http_port_t -p tcp 12345</code></p><h1 id="selinux-boolean-值">selinux boolean 值</h1><p>布尔值是 SELinux 中功能的开/关设置。开/关 SELinux 功能的设置有数百种，而且许多设置已预定义。您可以通过运行 getsebool -a，找出系统中已设置的布尔值。</p><h2 id="获取-boolean-值及附带的描述">获取 boolean 值，及附带的描述</h2><p><code>semanage boolean -l</code></p><h2 id="设置-boolean-值">设置 boolean 值</h2><p>开启<br /><code>setsebool -P samba_create_home_dirs on</code><br /><code>semanage boolean -m virt_use_comm -1</code><br />关闭<br /><code>setsebool -P samba_create_home_dirs off</code><br /><code>semanage boolean -m virt_use_comm -0</code></p>]]>
                    </description>
                    <pubDate>2021-08-14T23:11:28+08:00</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Zipkin 简介]]>
                    </title>
                    <link>https://seansun.xyz/archives/zipkin</link>
                    <description>
                            <![CDATA[<h2 id="第一映像">第一映像</h2><p><a href="https://zipkin.io/">zipkin</a>是一个分布式跟踪系统。用于收集分布式系统中的日志数据。拥有数据的收集和查找功能，现在可以通过docker镜像和jar包的方式运行。</p><p>如果日志文件中有trace ID，则可以直接跳至该trace ID。否则，您可以基于下面这些属性进行查询，例如service, operation name, tags and duration。zipkin会帮你总结，在服务中花费的时间百分比等情况。<br />查询方式如下:<br /><img src="https://seansun.xyz/upload/2020/06/zipkin%20filter-b72ebba1244848e091509f2f074991b3.png" alt="zipkin filter.png" /></p><p><strong>查询结果展示:</strong></p><p><img src="https://zipkin.io/public/img/web-screenshot.png" alt="查询展示" /></p><p><strong>服务依赖展示:</strong></p><p><img src="https://zipkin.io/public/img/dependency-graph.png" alt="服务依赖" /></p><h3 id="数据流图">数据流图</h3><p><img src="https://zipkin.io/public/img/architecture-1.png" alt="zipkin架构" /></p><h3 id="服务记录流程">服务记录流程:</h3><p><img src="https://seansun.xyz/upload/2020/06/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20200610224924-8497c2dc8e15400883a234287a9d4327.png" alt="微信截图_20200610224924.png" /></p><h2 id="组件">组件</h2><p><strong>Zipkin由4个组件组成:</strong></p><ul><li>collector</li><li>storage</li><li>search</li><li>web UI</li></ul><p><strong>Zipkin Collector</strong><br />trace data 到达Zipkin收集器守护程序后，将对其进行验证，存储和索引以供Zipkin收集器进行查找。</p><p><strong>Storage</strong><br />zipkin最初构建存储使用的是Cassandra ,现在也原生支持ElasticSearch and MySQL,另外可能还有一些第三方的扩展存储。</p><p><strong>Zipkin Query Service</strong><br />数据存储和索引之后就需要抽取展示它，查询的守护进程提供了一个简单的json api 来查找和提取traces。 数据主要的消费者就是WEB UI。</p><p><strong>Web UI</strong><br />WEB UI提供了方法来展示traces ,可以通过服务、时间和注释等方式来展示。WEB UI没有内置的认证(登录)服务。</p><h2 id="常见用法">常见用法</h2><p>trace相关的instrument对照列表见 <a href="https://zipkin.io/pages/tracers_instrumentation.html">tracers_instrumentation</a></p><p>常用spring cloud微服务应用可以引入相应的依赖:</p><pre><code class="language-xml">        &lt;dependency&gt;            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;            &lt;artifactId&gt;spring-cloud-starter-zipkin&lt;/artifactId&gt;        &lt;/dependency&gt;</code></pre><p>指定相应的配置如下即可</p><pre><code class="language-yml">spring:  zipkin:    base-url: http://zipkinservice  #指定zipkin service url    sender:      type: web # 指定传输方式为 http 方式，另外支持rabbitmq 和 kafka</code></pre>]]>
                    </description>
                    <pubDate>2020-06-10T22:20:08+08:00</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[HTTPS简单解析]]>
                    </title>
                    <link>https://seansun.xyz/archives/https简单解析</link>
                    <description>
                            <![CDATA[<ul><li><p>什么是SSL TLS Https X.509<br /><a href="https://baike.baidu.com/item/SSL">SSL</a>(Secure Sockets Layer 安全套接字协议),及其继任者传输层安全（Transport Layer Security，TLS）是为网络通信提供安全及数据完整性的一种安全协议。 TLS与SSL在传输层与应用层之间对网络连接进行加密。<br /><a href="https://zh.wikipedia.org/wiki/X.509">X.509</a>是密码学里公钥证书的格式标准。X.509证书已应用在包括TLS/SSL在内的众多网络协议里，同时它也用在很多非在线应用场景里，比如电子签名服务。X.509证书里含有公钥、身份信息（比如网络主机名，组织的名称或个体名称等）和签名信息（可以是证书签发机构CA的签名，也可以是自签名）。对于一份经由可信的证书签发机构签名或者可以通过其它方式验证的证书，证书的拥有者就可以用证书及相应的私钥来创建安全的通信，对文档进行数字签名。</p></li><li><p>SSL只能用在HTTP上吗？<br />SSL/TLS协议的基本思路是采用非对称加密法，服务器收到密文后，用自己的私钥解密。<br />SSL协议可用于保护正常运行于TCP之上的任何应用协议，如HTTP、FTP、SMTP或Telnet的通信，最常见的是用SSL来保护HTTP的通信。</p></li><li><p>SSL握手的过程<br />其握手的流程大概分为以下四步:<img src="https://seansun.xyz/upload/2020/6/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20200610201850-2117560c93f24e3d8544fe1e6889b838.png" alt="微信截图_20200610201850.png" /></p></li></ul><p>握手最后一步会交换对称加密秘钥作为会话秘钥（效率问题），对传输内容进行加密。</p>]]>
                    </description>
                    <pubDate>2020-06-08T20:34:24+08:00</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[AmqpTemplate]]>
                    </title>
                    <link>https://seansun.xyz/archives/amqptemplate</link>
                    <description>
                            <![CDATA[<p>本文档基于 <strong>spring AMQP 2.2.1RELEASE REFERENCE 文档</strong></p><p>与Spring框架和相关项目提供的许多其他高级抽象一样，Spring AMQP提供了一个&quot;<strong>Template</strong>&quot;，该<strong>Template</strong>起着核心作用。定义的主要操作接口为AmqpTemplate。这些操作只涵盖了发送和接收消息的一般行为。换句话说，它们并不是任何实现所特有的，从名称中就带有&quot;AMQP&quot;就能看出来。另一方面，该接口的实现与AMQP协议的实现绑定在一起。与JMS（本身是接口级API）不同，AMQP是线级（wire-level）协议。该协议的实现都提供了自己的客户端库，因此模板接口的每种实现都取决于特定的客户端库。当前，只有一个实现：<code>RabbitTemplate</code>。在下面的示例中，我们经常使用<code>AmqpTemplate</code>。但是，当您查看配置示例或实例化模板或调用设置程序的任何代码摘录时，您会看到实现类型（例如,RabbitTemplate）。<br />像之前提到的，AmqpTemplate 接口定义了所有发送和接收消息的基本操作。我们会浏览消息的发送和接收在<a href="https://docs.spring.io/spring-amqp/docs/2.2.1.RELEASE/reference/html/#sending-messages">Sending Messages</a>和 <a href="https://docs.spring.io/spring-amqp/docs/2.2.1.RELEASE/reference/html/#receiving-messages">Receiving Messages</a>两节.</p><h1 id="添加重试功能adding-retry-capabilities">添加重试功能(Adding Retry Capabilities)</h1><p>从1.3版开始，您现在可以将RabbitTemplate配置为使用RetryTemplate来帮助处理broker连接性问题。 有关完整信息，请参见<a href="https://github.com/spring-projects/spring-retry">spring-retry</a>项目。 以下仅是一个使用指数退避策略和默认<strong>SimpleRetryPolicy</strong>的示例，该示例在将异常抛出给调用方之前进行了三次尝试。</p><p>以下示例使用XML名称空间：</p><pre><code class="language-xml">&lt;rabbit:template id=&quot;template&quot; connection-factory=&quot;connectionFactory&quot; retry-template=&quot;retryTemplate&quot;/&gt;&lt;bean id=&quot;retryTemplate&quot; class=&quot;org.springframework.retry.support.RetryTemplate&quot;&gt;    &lt;property name=&quot;backOffPolicy&quot;&gt;        &lt;bean class=&quot;org.springframework.retry.backoff.ExponentialBackOffPolicy&quot;&gt;            &lt;property name=&quot;initialInterval&quot; value=&quot;500&quot; /&gt;            &lt;property name=&quot;multiplier&quot; value=&quot;10.0&quot; /&gt;            &lt;property name=&quot;maxInterval&quot; value=&quot;10000&quot; /&gt;        &lt;/bean&gt;    &lt;/property&gt;&lt;/bean&gt;</code></pre><p>以下是Java中使用<code>@Configuration</code>注解的示例：</p><pre><code class="language-java">@Beanpublic AmqpTemplate rabbitTemplate() {    RabbitTemplate template = new RabbitTemplate(connectionFactory());    RetryTemplate retryTemplate = new RetryTemplate();    ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();    backOffPolicy.setInitialInterval(500);    backOffPolicy.setMultiplier(10.0);    backOffPolicy.setMaxInterval(10000);    retryTemplate.setBackOffPolicy(backOffPolicy);    template.setRetryTemplate(retryTemplate);    return template;}</code></pre><p>从1.4版本开始，除了retryTemplate属性外，RabbitTemplate还支持recoveryCallback参数。该参数用作<code>RetryTemplate.execute(RetryCallback&lt;T, E&gt; retryCallback, RecoveryCallback&lt;T&gt; recoveryCallback)</code>的第二个参数.</p><pre><code class="language-java">retryTemplate.execute(    new RetryCallback&lt;Object, Exception&gt;() {        @Override        public Object doWithRetry(RetryContext context) throws Exception {            context.setAttribute(&quot;message&quot;, message);            return rabbitTemplate.convertAndSend(exchange, routingKey, message);        }    }, new RecoveryCallback&lt;Object&gt;() {        @Override        public Object recover(RetryContext context) throws Exception {            Object message = context.getAttribute(&quot;message&quot;);            Throwable t = context.getLastThrowable();            // Do something with message            return null;        }    });}</code></pre><p>在这种情况下，您<strong>不应该</strong>将RetryTemplate注入RabbitTemplate中。</p><h1 id="生产是异步的如何检测成功和失败publishing-is-asynchronoushow-to-detect-successes-and-failures">生产是异步的—如何检测成功和失败(Publishing is Asynchronous — How to Detect Successes and Failures)</h1><p>发布消息是一种异步机制，默认情况下，RabbitMQ会删除无法路由的消息。 为了成功发布，您将会收到异步的确认，如<a href="https://docs.spring.io/spring-amqp/docs/2.2.1.RELEASE/reference/html/#template-confirms"><strong>Publisher Confirms and Returns</strong></a>所述。 考虑两种故障情况：</p><ul><li>发布到exchange ，但没有匹配的目标队列。</li><li>发布到不存在的exchange。</li></ul><p>第一种情况在生产者返回中说明了，具体见 <a href="https://docs.spring.io/spring-amqp/docs/2.2.1.RELEASE/reference/html/#template-confirms">Publisher Confirms and Returns</a>。</p><p>对于第二种情况，该消息将被丢弃，并且不会生成返回。 地层的通道将被关闭并抛出一个异常。 默认情况下，此异常会有日志，您也可以向CachingConnectionFactory注册ChannelListener以获得此类事件的通知。 以下示例显示如何添加ConnectionListener：</p><pre><code class="language-java">this.connectionFactory.addConnectionListener(new ConnectionListener() {    @Override    public void onCreate(Connection connection) {    }    @Override    public void onShutDown(ShutdownSignalException signal) {        ...    }});</code></pre><p>您可以检查signal的reason属性以确定发生的原因。<br />要检测在发送线程上的异常，可以在RabbitTemplate上设置ChannelTransacted（true）并在txCommit方法上检测到异常。 但是，transactions会严重影响性能，如果只是一个用例的话，请仔细考虑性能下降（impede performance）这一点。</p><h1 id="生产者确认和返回publisher-confirms-and-returns">生产者确认和返回(Publisher Confirms and Returns)</h1><p>RabbitTemplate实现了AmqpTemplate，支持生产者确认并返回。</p><p>对于返回的messages，templates的 <code>mandatory</code> 属性必须设置为true，或者对于一些特殊的messages必须将expired-expression评估为true。 此功能要求CachingConnectionFactory的PublisherReturns属性设置为true（请参阅<a href="https://docs.spring.io/spring-amqp/docs/2.2.1.RELEASE/reference/html/#cf-pub-conf-ret">Publisher Confirms and Returns</a>）。 通过调用<code>setReturnCallback（ReturnCallback callback）</code>注册<code>RabbitTemplate.ReturnCallback</code>，将返回值发送到客户端。 回调必须实现以下方法：</p><pre><code class="language-java">void returnedMessage(Message message, int replyCode, String replyText,          String exchange, String routingKey);</code></pre><p>每个RabbitTemplate只支持一个 ReturnCallback。</p><p>对于生产者确认（publisher confirms，也称为publisher acknowledgements），template 需要一个CachingConnectionFactory，并将其的PublisherConfirm属性设置为ConfirmType.CORRELATED。 通过调用setConfirmCallback（ConfirmCallback callback）注册RabbitTemplate.ConfirmCallback，将确认发送到客户端。 回调必须实现此方法：</p><pre><code class="language-java">void confirm(CorrelationData correlationData, boolean ack, String cause);</code></pre><p>CorrelationData是客户端在发送原始消息时提供的对象。 ack(确认接收)对应于ack字段的值是true，而对于nack则是false。 对于nack实例，原因可能包含在nack的reason 字段中（如果在生成nack时可用）。 一个示例是将消息发送到不存在的exchange时。 在这种情况下，broker 将关闭channel。 reason 中包含了关闭的原因。 The cause已在1.4版中添加。</p><p>一个RabbitTemplate只能支持一个 ConfirmCallback 。</p><p>从2.1版开始，CorrelationData对象具有一个ListenableFuture，可用来获取结果，而不是在模板上使用ConfirmCallback。 以下示例显示如何配置CorrelationData实例：</p><pre><code class="language-java">CorrelationData cd1 = new CorrelationData();this.templateWithConfirmsEnabled.convertAndSend(&quot;exchange&quot;, queue.getName(), &quot;foo&quot;, cd1);assertTrue(cd1.getFuture().get(10, TimeUnit.SECONDS).isAck());</code></pre><p>由于它是ListenableFuture <Confirm>，因此可以在准备就绪时用<code>get（）</code>方法获取结果，也可以为异步回调添加侦听器。 Confirm对象是一个简单的bean，具有2个属性：ack和reason（对于nack实例）。broker生成的nack实例未填充reason 字段。 由框架生成的nack实例则包含reason 字段（例如，在ack实例未完成时关闭连接）。<br />另外，当同时启用确认和返回（ confirms and returns）时，将使用返回的消息填充CorrelationData。 在使用ack设置CorrelationData的future之前发生确认和返回是被承诺的。</p>]]>
                    </description>
                    <pubDate>2019-12-09T22:32:37+08:00</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Spring AMQP连接和资源管理]]>
                    </title>
                    <link>https://seansun.xyz/archives/springamqp连接和资源管理</link>
                    <description>
                            <![CDATA[<p>本文档基于 <strong>spring AMQP 2.2.1RELEASE REFERENCE 文档</strong></p><p>尽管我们之前描述的AMQP模型是通用的，并且适用于所有实现，但是当我们进行资源管理时，详细信息都基于特定的 <strong>broker</strong> 实现。因此，在本节中，我们专注“ spring-rabbit”模块的实现，因为现在，RabbitMQ是唯一受支持的AMQP实现。</p><p>用于管理与RabbitMQ代理的连接的中央组件是<strong>ConnectionFactory</strong>接口。<br /><strong>ConnectionFactory</strong>实现的职责是提供<code>**org.springframework.amqp.rabbit.connection.Connection**</code>的实例，该实例是<code>**com.rabbitmq.client.Connection**</code>的包装。我们提供的唯一具体实现是 <strong>CachingConnectionFactory</strong> ，默认情况下，它将建立一个可以由应用程序共享的单个连接代理。由于与AMQP进行消息传递的“<strong>unit of work</strong>”实际上是一个“<strong>channel</strong>”（在某些方面，它类似于JMS中的连接和会话之间的关系），因此可以共享连接。连接实例提供了<strong>createChannel</strong>方法。 <strong>CachingConnectionFactory</strong>实现支持这些通道的缓存，并且根据通道是否是事务性的，为通道维护单独的缓存。创建<strong>CachingConnectionFactory</strong>实例时，可以通过构造函数提供 “<strong>hostname</strong>”（应该是指的spring提供的host属性）。您还应该提供 “<strong>username</strong>” 和 “<strong>password</strong>” 属性。要配置通道缓存的大小（默认值为25），可以调用 <strong>setChannelCacheSize</strong> 方法。</p><p>从1.3版开始，您可以配置<strong>CachingConnectionFactory</strong>来缓存连接或者仅缓存通道。在这种情况下，对<strong>createConnection</strong>的每次调用都会创建一个新的连接（或从缓存中检索一个空闲的连接）。关闭连接会将其返回到缓存（如果尚未达到缓存上限）。在此类连接上创建的通道也将被缓存。在某些环境中，使用单独（seperate,这里应该是说一个连接一个通道）的连接可能很有用，例如从一个HA群集消费、连接一个已经连接到不同的群集成员负载平衡器，等等。要缓存连接，请将<strong>cacheMode</strong>设置为<strong>CacheMode.CONNECTION</strong>。</p><p>重要的是要了解，缓存大小（默认情况下）不是对缓存的限制，而仅仅是说明可以缓存的channel数。缓存大小为10，实际上可以使用任何数量的channel。如果使用了10个以上的channel，则10个channel进入缓存，其余部分则关闭。</p><p>从版本1.6开始，默认的channel缓存大小已从1增加到25。在大容量，多线程环境中，较小的缓存意味着将以较高的速率创建和关闭通道。增加默认缓存大小可以避免这种创建缓存的开销。您应该通过RabbitMQ Admin UI监视正在使用的通道，如果看到许多正在创建和关闭的通道，请考虑进一步增加缓存大小。缓存仅按需增长（以适应应用程序的并发要求），因此此更改不会影响现有的小批量应用程序。</p><p>从1.4.2版开始，<strong>CachingConnectionFactory</strong>具有一个名为<strong>channelCheckoutTimeout</strong>的属性。当此属性大于零时，<strong>channelCacheSize</strong>成为可在连接上创建的通道数的限制。如果达到限制，则调用线程将阻塞，直到某个通道可用或达到此超时为止，在这种情况下，将抛出<strong>AmqpTimeoutException</strong></p><p>以下示例显示了如何创建新连接：</p><pre><code class="language-java">CachingConnectionFactory connectionFactory = new CachingConnectionFactory(&quot;somehost&quot;);connectionFactory.setUsername(&quot;guest&quot;);connectionFactory.setPassword(&quot;guest&quot;);Connection connection = connectionFactory.createConnection();</code></pre><p>使用XML时，配置如以下示例：</p><pre><code class="language-xml">&lt;bean id=&quot;connectionFactory&quot;      class=&quot;org.springframework.amqp.rabbit.connection.CachingConnectionFactory&quot;&gt;    &lt;constructor-arg value=&quot;somehost&quot;/&gt;    &lt;property name=&quot;username&quot; value=&quot;guest&quot;/&gt;    &lt;property name=&quot;password&quot; value=&quot;guest&quot;/&gt;&lt;/bean&gt;</code></pre><p>可以使用Rabbit命名空间快速而方便地创建ConnectionFactory，如下所示：</p><pre><code class="language-xml">&lt;rabbit:connection-factory id=&quot;connectionFactory&quot;/&gt;</code></pre><p>在大多数情况下，这种方法是可取的，因为框架可以为您选择最佳的默认值。创建的实例是CachingConnectionFactory。请记住，通道的默认缓存大小为25。如果要缓存更多的通道，请通过设置'channelCacheSize'属性设置更大的值。在XML中，如下所示：</p><pre><code class="language-xml">&lt;bean id=&quot;connectionFactory&quot;      class=&quot;org.springframework.amqp.rabbit.connection.CachingConnectionFactory&quot;&gt;    &lt;constructor-arg value=&quot;somehost&quot;/&gt;    &lt;property name=&quot;username&quot; value=&quot;guest&quot;/&gt;    &lt;property name=&quot;password&quot; value=&quot;guest&quot;/&gt;    &lt;property name=&quot;channelCacheSize&quot; value=&quot;50&quot;/&gt;&lt;/bean&gt;</code></pre><p>同样，使用命名空间，您可以添加<strong>channel-cache-size</strong>属性，如下所示：</p><pre><code class="language-xml">&lt;rabbit:connection-factory    id=&quot;connectionFactory&quot; channel-cache-size=&quot;50&quot;/&gt;</code></pre><p>默认的缓存模式是CHANNEL，但是您可以将其配置为缓存连接。在以下示例中，我们使用connection-cache-size：</p><pre><code class="language-xml">&lt;rabbit:connection-factory    id=&quot;connectionFactory&quot; cache-mode=&quot;CONNECTION&quot; connection-cache-size=&quot;25&quot;/&gt;</code></pre><p>您可以使用名称空间提供主机和端口属性，如下所示：</p><pre><code class="language-xml">&lt;rabbit:connection-factory    id=&quot;connectionFactory&quot; host=&quot;somehost&quot; port=&quot;5672&quot;/&gt;</code></pre><p>或者，如果在群集环境中运行，则可以使用addresss属性，如下所示：</p><pre><code class="language-xml">&lt;rabbit:connection-factory    id=&quot;connectionFactory&quot; addresses=&quot;host1:5672,host2:5672&quot; shuffle-addresses=&quot;true&quot;/&gt;</code></pre><p>以下示例注入了一个自定义线程工厂，该工厂在线程名称前添加了&quot;<strong>Rabbitmq-</strong>&quot;前缀：</p><pre><code class="language-xml">&lt;rabbit:connection-factory id=&quot;multiHost&quot; virtual-host=&quot;/bar&quot; addresses=&quot;host1:1234,host2,host3:4567&quot;    thread-factory=&quot;tf&quot;    channel-cache-size=&quot;10&quot; username=&quot;user&quot; password=&quot;password&quot; /&gt;&lt;bean id=&quot;tf&quot; class=&quot;org.springframework.scheduling.concurrent.CustomizableThreadFactory&quot;&gt;    &lt;constructor-arg value=&quot;rabbitmq-&quot; /&gt;&lt;/bean&gt;</code></pre><h1 id="naming-connection-命名连接">Naming Connection 命名连接</h1><p>从1.7版本开始，提供了<strong>ConnectionNameStrategy</strong>属性用于注入到<strong>AbstractionConnectionFactory</strong>中。生成的名称用于特定应用程序对RabbitMQ连接的标识。如果RabbitMQ服务器支持，则连接名称将显示在管理UI中。此值不必唯一，也不能用作连接标识符（例如，在HTTP API请求中）。该值应该是人类可读的，并且是connection_name项下ClientProperty的一部分。您可以使用简单的Lambda，如下所示：</p><pre><code class="language-java">connectionFactory.setConnectionNameStrategy(connectionFactory -&gt; &quot;MY_CONNECTION&quot;);</code></pre><p><strong>ConnectionFactory</strong> 参数可用于通过某些逻辑来区分目标连接名称。 默认情况下，将使用<strong>AbstractConnectionFactory</strong>的beanName，代表对象的十六进制字符串以及内部计数器来生成connection_name。<code>&lt;rabbit：connection-factory&gt;</code>命名空间组件也提供了<strong>connection-name-strategy</strong>属性。<br /><strong>SimplePropertyValueConnectionNameStrategy</strong>对于<strong>ConnectionNameStrategy</strong>的实现将连接名称设置为应用程序属性。 您可以将其声明为@Bean并将其注入连接工厂，如以下示例所示：</p><pre><code class="language-java">@Beanpublic ConnectionNameStrategy cns() {    return new SimplePropertyValueConnectionNameStrategy(&quot;spring.application.name&quot;);}@Beanpublic ConnectionFactory rabbitConnectionFactory(ConnectionNameStrategy cns) {    CachingConnectionFactory connectionFactory = new CachingConnectionFactory();    ...    connectionFactory.setConnectionNameStrategy(cns);    return connectionFactory;}</code></pre><p>属性必须在应用上下文的<code>Environment</code>中存在。</p><h1 id="阻塞的连接和资源限制">阻塞的连接和资源限制</h1><p>连接可能被阻止与来自与内存警报相对应的<strong>broker</strong>进行交互。 从2.0版开始，<code>org.springframework.amqp.rabbit.connection.Connection</code>可以与<code>com.rabbitmq.client.BlockedListener</code>实例一起使用，以通知连接<strong>block</strong>和<strong>unblock</strong>的事件。 另外，<strong>AbstractConnectionFactory</strong>通过其内部的<strong>BlockedListener</strong>实现发出<strong>ConnectionBlockedEvent</strong>和<strong>ConnectionUnblockedEvent</strong>事件。 这些使您可以提供应用程序逻辑，以对<strong>broker</strong>上的问题做出适当反应，采取一些对应措施。<br />从2.0.2版开始，RabbitTemplate具有一个配置选项，可以在不使用事务的情况下，自动使用第二个连接工厂。 有关更多信息，请参见<a href="https://docs.spring.io/spring-amqp/docs/2.2.1.RELEASE/reference/html/#separate-connection">这里</a>。 生产者连接的ConnectionNameStrategy与主策略相同，在调用方法的结果后附加.publisher。<br />从版本1.7.7开始，提供了<strong>AmqpResourceNotAvailableException</strong>，当<code>SimpleConnection.createChannel()</code>无法创建<strong>Channel</strong>时（例如，由于达到了channelMax限制并且高速缓存中没有可用的通道），将抛出此异常。 您可以在RetryPolicy中使用此异常，以在某些回退之后恢复操作。</p><h1 id="配置底层的客户端连接工厂">配置底层的客户端连接工厂</h1><p><strong>CachingConnectionFactory</strong> 使用 <strong>Rabbit Client 包</strong> 里面的<strong>ConnectionFactory</strong>实例。 在CachingConnectionFactory上设置的许多配置属性（例如，host，port，username，password，requestedHeartBeat、connectionTimeout）将会被传递到对应的等价属性。 要设置其他属性（例如，clientProperties），您可以定义Rabbit factory 的实例，并使用CachingConnectionFactory的适当构造函数为其提供引用。 如果使用名称空间（xml）的的时候，您需要在connection-factory属性中提供对已配置工厂的引用。 为了方便起见，提供了factory bean来简化Spring应用程序上下文中配置连接工厂。</p><pre><code class="language-xml">&lt;rabbit:connection-factory      id=&quot;connectionFactory&quot; connection-factory=&quot;rabbitConnectionFactory&quot;/&gt;</code></pre><h1 id="rabbitconnectionfactorybean-和配置-ssl">RabbitConnectionFactoryBean 和配置 SSL</h1><p>从版本1.4开始，提供了一个便捷的<strong>RabbitConnectionFactoryBean</strong>，该类使用依赖项注入在底层客户端连接工厂上实现SSL属性的便捷配置。 其他setter委托给底层的工厂。 以前，您必须以编程方式配置SSL选项。 以下示例显示了如何配置<strong>RabbitConnectionFactoryBean</strong>：</p><pre><code class="language-xml">&lt;rabbit:connection-factory id=&quot;rabbitConnectionFactory&quot;    connection-factory=&quot;clientConnectionFactory&quot;    host=&quot;${host}&quot;    port=&quot;${port}&quot;    virtual-host=&quot;${vhost}&quot;    username=&quot;${username}&quot; password=&quot;${password}&quot; /&gt;&lt;bean id=&quot;clientConnectionFactory&quot;        class=&quot;org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean&quot;&gt;    &lt;property name=&quot;useSSL&quot; value=&quot;true&quot; /&gt;    &lt;property name=&quot;sslPropertiesLocation&quot; value=&quot;file:/secrets/rabbitSSL.properties&quot;/&gt;&lt;/bean&gt;</code></pre><p>有关配置SSL的信息,省略keyStore和trustStore配置以通过SSL进行连接而无需证书验证的方法请参见<a href="https://www.rabbitmq.com/ssl.html">RabbitMQ文档</a>。下一个示例显示如何提供密钥和信任库配置。<br />sslPropertiesLocation属性是一个Spring Resource，指向包含以下配置的properites文件：</p><pre><code class="language-properties">keyStore=file:/secret/keycert.p12trustStore=file:/secret/trustStorekeyStore.passPhrase=secrettrustStore.passPhrase=secret</code></pre><p><strong>keyStore</strong>和<strong>truststore</strong>是指向<strong>Spring Resources</strong>指向的文件。 通常，此<strong>properties file</strong>由操作系统保护，而应用程序具有read 权限。<br />从Spring AMQP 1.5版开始，您可以直接在工厂bean上设置这些属性。 如果同时提供了工厂上设置的属性和sslPropertiesLocation，则后者中的属性将覆盖工厂上设置的值。</p><h1 id="连接到集群">连接到集群</h1><p>要连接到群集，需要在<strong>CachingConnectionFactory</strong>上配置<strong>address</strong>属性：</p><pre><code class="language-java">@Beanpublic CachingConnectionFactory ccf() {    CachingConnectionFactory ccf = new CachingConnectionFactory();    ccf.setAddresses(&quot;host1:5672,host2:5672,host3:5672&quot;);    return ccf;}</code></pre><p>底层的<strong>connection factory</strong>将尝试依次顺序连接到每个主机。 从版本2.1.8开始，可以通过将<strong>shuffleAddresses</strong>属性设置为<strong>true</strong>来使用随机顺序连接。 随机连接应该在建立新连接之前设置。</p><pre><code class="language-java">@Beanpublic CachingConnectionFactory ccf() {    CachingConnectionFactory ccf = new CachingConnectionFactory();    ccf.setAddresses(&quot;host1:5672,host2:5672,host3:5672&quot;);    ccf.setShuffleAddresses(true);    return ccf;}</code></pre><h1 id="路由connection-factory">路由Connection Factory</h1><p>从1.3版开始，引入了<strong>AbstractRoutingConnectionFactory</strong>。 该工厂提供了一种机制，可以为多个<strong>ConnectionFactory</strong>配置映射并在运行时通过一些<strong>lookupKey</strong>确定目标<strong>ConnectionFactory</strong>。 通常，实现检查线程绑定上下文。 为了方便起见，<strong>Spring AMQP</strong>提供了<strong>SimpleRoutingConnectionFactory</strong>，该工厂从<strong>SimpleResourceHolder</strong>获取当前线程绑定的<strong>lookupKey</strong>。 以下示例说明如何在<strong>XML</strong>和<strong>Java</strong>中配置<strong>SimpleRoutingConnectionFactory</strong>：</p><pre><code class="language-xml">&lt;bean id=&quot;connectionFactory&quot;      class=&quot;org.springframework.amqp.rabbit.connection.SimpleRoutingConnectionFactory&quot;&gt;&lt;property name=&quot;targetConnectionFactories&quot;&gt;&lt;map&gt;&lt;entry key=&quot;#{connectionFactory1.virtualHost}&quot; ref=&quot;connectionFactory1&quot;/&gt;&lt;entry key=&quot;#{connectionFactory2.virtualHost}&quot; ref=&quot;connectionFactory2&quot;/&gt;&lt;/map&gt;&lt;/property&gt;&lt;/bean&gt;&lt;rabbit:template id=&quot;template&quot; connection-factory=&quot;connectionFactory&quot; /&gt;</code></pre><pre><code class="language-java">public class MyService {    @Autowired    private RabbitTemplate rabbitTemplate;    public void service(String vHost, String payload) {        SimpleResourceHolder.bind(rabbitTemplate.getConnectionFactory(), vHost);        rabbitTemplate.convertAndSend(payload);        SimpleResourceHolder.unbind(rabbitTemplate.getConnectionFactory());    }}</code></pre><p>资源使用之后解绑是非常重要的，更多信息，可以参见<strong>AbstractRoutingConnectionFactory</strong></p><p>从1.4版本开始，<strong>RabbitTemplate</strong>支持<strong>SpEL</strong>  <strong>sendConnectionFactorySelectorExpression</strong>和<strong>receiveConnectionFactorySelectorExpression</strong>属性，这些属性在每个AMQP协议交互操作（<strong>send</strong>, <strong>sendAndReceive</strong>, <strong>receive</strong>, <strong>receiveAndReply</strong>）上进行解析，最终解析为供<strong>AbstractRoutingConnectionFactory</strong>使用的<strong>lookupKey</strong>值。 您可以在表达式中使用bean引用，例如<code>@ vHostResolver.getVHost(#root)</code>。 对于发送操作，要发送的消息是<strong>root</strong>解析的对象。 对于接收操作，<strong>queueName</strong>是<strong>root</strong>解析的对象。</p><p>路由算法如下：如果选择表达式为<strong>null</strong>或被解析为<strong>null</strong>或提供的<strong>ConnectionFactory</strong>不是<strong>AbstractRoutingConnectionFactory</strong>的实例，则所有操作均依赖已有的<strong>ConnectionFactory</strong>实现。 如果评估结果不为<strong>null</strong>，但没有适用于该<strong>lookupKey</strong>的目标<strong>ConnectionFactory</strong>，并且使用<code>lenientFallback = true</code>配置<strong>AbstractRoutingConnectionFactory</strong>，则会发生和先前相同的情况。 在使用<strong>AbstractRoutingConnectionFactory</strong>的情况下，它会回退到基于<strong>defineCurrentLookupKey()</strong> 的路由实现。 但是，如果<code>lenientFallback = false</code>，则抛出<strong>IllegalStateException</strong>。</p><p>命名空间(xml)还提供了<code>&lt;rabbit：template&gt;</code>组件上的<strong>send-connection-factory-selector-expression</strong>和<strong>receive-connection-factory-selector-expression</strong>属性来支持路由key。</p><p>另外，从1.4版开始，您可以在<strong>listener container</strong>中配置<strong>routing connection factory</strong>。 在这种情况下，队列名称列表将用作查找关键字。 例如，如果使用<code>setQueueNames(&quot;thing1&quot;，&quot;thing2&quot;)</code>配置容器，则查找键为<code>[thing1,thing]</code>（请注意，键中没有空格）。</p><p>从1.6.9版开始，可以通过在 <strong>listener container</strong>上使用<strong>setLookupKeyQualifier</strong>将<strong>qualifier <strong>添加到查找键(<strong>lookup key</strong>)。 这样就可以侦听具有相同名称但在不同</strong>virtual host</strong>中的队列（每个虚拟主机中都有一个<strong>connection factory</strong>）。</p><p>例如，在使用lookup key qualifier thing1和一个container 侦听队列thing2的情况下，可以注册目标连接工厂的查找键为<strong>thing1[thing2]</strong>。</p><h1 id="队列亲和性和-localizedqueueconnectionfactory">队列亲和性和 LocalizedQueueConnectionFactory</h1><p>在群集中使用HA队列时，为了获得最佳性能，您可能需要连接到mater队列所在的物理broker。可以使用多个代理地址配置CachingConnectionFactory。这种情况下客户端尝试按顺序连接来进行故障转移。LocalizedQueueConnectionFactory使用管理插件提供的REST API来确定在哪个节点上是master队列。 然后，它创建（或从缓存检索）仅连接到该节点的CachingConnectionFactory。 如果连接失败，那么将确定新的master节点，并且消费者将连接到新的master节点。 如果无法确定队列的物理位置，则使用默认的连接工厂配置LocalizedQueueConnectionFactory，在这种情况下，它将照常连接到群集。</p><p><strong>LocalizedQueueConnectionFactory</strong>是一个<strong>RoutingConnectionFactory</strong>，<strong>SimpleMessageListenerContainer</strong>使用队列名称作为查找关键字，如上面的 &quot;路由 Connection Factory&quot; 中所述。</p><p>以下示例配置显示了如何使用Spring Boot的<strong>RabbitProperty</strong>配置工厂：</p><pre><code class="language-java">@Autowiredprivate RabbitProperties props;private final String[] adminUris = { &quot;https://host1:15672&quot;, &quot;https://host2:15672&quot; };private final String[] nodes = { &quot;rabbit@host1&quot;, &quot;rabbit@host2&quot; };@Beanpublic ConnectionFactory defaultConnectionFactory() {    CachingConnectionFactory cf = new CachingConnectionFactory();    cf.setAddresses(this.props.getAddresses());    cf.setUsername(this.props.getUsername());    cf.setPassword(this.props.getPassword());    cf.setVirtualHost(this.props.getVirtualHost());    return cf;}@Beanpublic ConnectionFactory queueAffinityCF(        @Qualifier(&quot;defaultConnectionFactory&quot;) ConnectionFactory defaultCF) {    return new LocalizedQueueConnectionFactory(defaultCF,            StringUtils.commaDelimitedListToStringArray(this.props.getAddresses()),            this.adminUris, this.nodes,            this.props.getVirtualHost(), this.props.getUsername(), this.props.getPassword(),            false, null);}</code></pre><p>请注意，前三个参数addresses，adminUris和nodes都是数组。 这些是适当的，因为当容器尝试连接到队列时，它确定队列在哪个node上是master队列，并连接到同一数组位置对应的的地址。</p><h1 id="publisher-confirms-and-returns">Publisher Confirms and Returns</h1><p>通过将<strong>CachingConnectionFactory</strong>的<strong>PublisherConfirmType</strong>属性设置为<code>ConfirmType.CORRELATED</code>并将<strong>PublisherReturns</strong>属性设置为<code>'true'</code>，可以开启发布消息的确认（具有相关性）和返回消息。<br />设置这些选项后，由工厂创建的<strong>Channel</strong>实例将被包装在<strong>PublisherCallbackChannel</strong>中，该<strong>Publisher</strong>用于增加回调。当获得这样的channel时，客户端可以向该channel注册PublisherCallbackChannel.Listener。 PublisherCallbackChannel实现包含将确认或返回到适当的listener的逻辑。</p><h1 id="connection-and-channel-listeners">Connection and Channel Listeners</h1><p>连接工厂支持注册<strong>ConnectionListener</strong>和<strong>ChannelListener</strong>实现。 这使您可以接收有关<strong>Connection</strong>和<strong>Channel</strong>相关事件的通知。（当建立Connection时，RabbitAdmin使用ConnectionListener来执行声明(<strong>declarations</strong>)-有关更多信息，请参见<a href="https://docs.spring.io/spring-amqp/docs/2.2.1.RELEASE/reference/html/#automatic-declaration">这里</a>）。 以下代码显示了ConnectionListener接口定义：</p><pre><code class="language-java">@FunctionalInterfacepublic interface ConnectionListener {    void onCreate(Connection connection);    default void onClose(Connection connection) {    }    default void onShutDown(ShutdownSignalException signal) {    }}</code></pre><p>从2.0版开始，<code>org.springframework.amqp.rabbit.connection.Connection</code>对象可以支持与<code>com.rabbitmq.client.BlockedListener</code>实例一起使用，使connection被block和unblock的事件进行通知。 以下示例显示ChannelListener接口定义：</p><pre><code class="language-java">@FunctionalInterfacepublic interface ChannelListener {    void onCreate(Channel channel, boolean transactional);    default void onShutDown(ShutdownSignalException signal) {    }}</code></pre><h1 id="记录通道关闭事件logging-channel-close-events">记录通道关闭事件（Logging Channel Close Events）</h1><p>1.5版引入了一种机制，使用户可以控制日志记录级别。<br />CachingConnectionFactory使用默认策略来记录通道关闭，如下所示：</p><ul><li>正常通道关闭（200 OK）不会被记录。</li><li>如果通道由于被动队列（passive queue）声明失败而关闭，那么它将在调试级别记录。</li><li>如果由于消费者排他使<code>basic.consume</code>被拒绝进而导致的通道关闭，那么它将以<strong>INFO</strong>级别记录。</li><li>其他所有日志均以<strong>ERROR</strong>级别记录。</li></ul><p>若要修改此行为，可以在<strong>CachingConnectionFactory</strong>的<strong>closeExceptionLogger</strong>属性中注入自定义的<strong>ConditionalExceptionLogger</strong>。</p><h1 id="运行时缓存属性runtime-cache-properties">运行时缓存属性（Runtime Cache Properties）</h1><p>从1.6版开始，<strong>CachingConnectionFactory</strong>现在通过<strong>getCacheProperties</strong>方法提供缓存统计信息。 这些统计信息可用于调整缓存以在生产中对其进行优化。 例如，<strong>the high water marks</strong>可用于确定是否应增加缓存大小。 如果等于缓存大小，则可能要考虑进一步增加。 下表描述了<code>CacheMode.CHANNEL</code>属性：</p><p><strong>Table 1. Cache properties for CacheMode.CHANNEL</strong></p><table><thead><tr><th>属性</th><th>意义</th></tr></thead><tbody><tr><td>connectionName</td><td>由ConnectionNameStrategy生成的连接的名称。</td></tr><tr><td>channelCacheSize</td><td>当前配置的允许空闲的最大通道数。</td></tr><tr><td>localPort</td><td>连接的本地端口（如果有）。这通常与rabbitMQ Admin UI上的连接和通道是相关的。</td></tr><tr><td>idleChannelsTx</td><td>当前空闲（缓存）的事务通道的数量。</td></tr><tr><td>idleChannelsNotTx</td><td>当前空闲（缓存）的非事务通道的数量。</td></tr><tr><td>idleChannelsTxHighWater</td><td>同时空闲（缓存）的最大事务通道数。</td></tr><tr><td>idleChannelsNotTxHighWater</td><td>同时空闲（缓存）的非事务通道的最大数量。</td></tr></tbody></table><p>接下来的表格描述了<code>CacheMode.CONNECTION</code> 属性:</p><p><strong>Table 2. Cache properties for CacheMode.CONNECTION</strong></p><table><thead><tr><th>属性</th><th><strong>意义</strong></th></tr></thead><tbody><tr><td>connectionName:<localPort></td><td>由<code>ConnectionNameStrategy</code>生成的名字.</td></tr><tr><td><code>openConnections</code></td><td>表示与broker连接的连接对象的数量。</td></tr><tr><td><code>channelCacheSize</code></td><td>当前配置的允许空闲的最大通道数。</td></tr><tr><td><code>connectionCacheSize</code></td><td>当前配置的允许空闲的最大连接数。</td></tr><tr><td><code>idleConnections</code></td><td>当前空闲的连接数。</td></tr><tr><td><code>idleConnectionsHighWater</code></td><td>同时空闲的最大连接数。</td></tr><tr><td><code>idleChannelsTx:</code></td><td>连接当前空闲（缓存）的事务通道的数量。 属性名称的localPort部分与RabbitMQ Admin UI上的连接和通道对应。</td></tr><tr><td><code>idleChannelsNotTx:</code></td><td>连接当前空闲（缓存）的非事务通道的数量。 属性名称的localPort部分可用于与RabbitMQ Admin UI上的连接和通道对应。</td></tr><tr><td><code>idleChannelsTxHighWater:</code></td><td>同时空闲（缓存）的最大事务通道数。 属性名称的localPort部分可用于与RabbitMQ Admin UI上的连接和通道关联。</td></tr><tr><td><code>idleChannelsNotTxHighWater:</code></td><td>同时空闲（缓存）的非事务通道的最大数量。 属性名称的localPort部分可用于与RabbitMQ Admin UI上的连接和通道关联。</td></tr></tbody></table><p><code>cacheMode</code> 属性 (<code>CHANNEL</code> or <code>CONNECTION</code>) 也被包含在返回对象内.</p><p><img src="https://docs.spring.io/spring-amqp/docs/2.2.1.RELEASE/reference/html/images/cacheStats.png" alt="cacheStats" /></p><p>Figure 1. JVisualVM 示例</p><h1 id="rabbitmq-自动-connectiontopology-恢复">RabbitMQ 自动 Connection/Topology 恢复</h1><p>Since the first version of Spring AMQP, the framework has provided its own connection and channel recovery in the event of a broker failure. Also, as discussed in <a href="https://docs.spring.io/spring-amqp/docs/2.2.1.RELEASE/reference/html/#broker-configuration">Configuring the Broker</a>, the <code>RabbitAdmin</code> re-declares any infrastructure beans (queues and others) when the connection is re-established. It therefore does not rely on the <a href="https://www.rabbitmq.com/api-guide.html#recovery">auto-recovery</a> that is now provided by the <code>amqp-client</code> library. Spring AMQP now uses the <code>4.0.x</code> version of <code>amqp-client</code>, which has auto recovery enabled by default. Spring AMQP can still use its own recovery mechanisms if you wish, disabling it in the client, (by setting the <code>automaticRecoveryEnabled</code> property on the underlying <code>RabbitMQ connectionFactory</code> to <code>false</code>). However, the framework is completely compatible with auto-recovery being enabled. This means any consumers you create within your code (perhaps via <code>RabbitTemplate.execute()</code>) can be recovered automatically.</p><p>从Spring AMQP的第一个版本开始，该框架在代理发生故障的情况下提供了自己的连接和通道恢复。 另外，如 <a href="https://docs.spring.io/spring-amqp/docs/2.2.1.RELEASE/reference/html/#broker-configuration">Configuring the Broker</a>中所述，当重新建立连接时，RabbitAdmin会重新声明所有底层的Bean（队列和其他）。 因此，它不依赖于amqp-client库现在提供的自动恢复。 Spring AMQP现在使用amqp-client的4.0.x版本，该库默认情况下启用了<a href="https://www.rabbitmq.com/api-guide.html#recovery">auto-recovery</a>。 如果需要，Spring AMQP仍可以使用其自己的恢复机制，只需在amqp-client客户端将其禁用（通过将基础RabbitMQ connectionFactory的automaticRecoveryEnabled属性设置为false）。 但是，该框架与启用的自动恢复完全兼容。 这意味着您在代码中创建的所有消费者（可能通过RabbitTemplate.execute()）都可以自动恢复。</p><blockquote></blockquote>]]>
                    </description>
                    <pubDate>2019-12-04T22:11:06+08:00</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Spring AMQP 模型简介]]>
                    </title>
                    <link>https://seansun.xyz/archives/springamqpapi简单介绍</link>
                    <description>
                            <![CDATA[<p>本文档基于 <strong>spring AMQP 2.2.1RELEASE REFERENCE 文档</strong></p><h1 id="基础组件">基础组件</h1><p>基础组件一共有四个Message、Exchange、Queue、Binding。</p><ul><li><strong>Message</strong><br />0-9-1 AMQP规范未定义Message类或接口。而是在执行诸如basicPublish（）之类的操作时，将内容作为字节数组参数传递，并将其他属性作为单独的参数传递。 Spring AMQP将Message类定义为更通用的AMQP领域模型表示形式的一部分。 Message类的目的是将正文和属性封装在一个实例中，以便使API更加简单。下面的示例显示Message类的定义：</li></ul><pre><code class="language-java">public class Message {    private final MessageProperties messageProperties;    private final byte[] body;    public Message(byte[] body, MessageProperties messageProperties) {        this.body = body;        this.messageProperties = messageProperties;    }    public byte[] getBody() {        return this.body;    }    public MessageProperties getMessageProperties() {        return this.messageProperties;    }}</code></pre><p>MessageProperties接口定义了几个常用属性，例如“ messageId”，“ timestamp”，“ contentType”等。您还可以通过调用setHeader（String key，Object value）方法，使用用户定义的“header”扩展这些属性。</p><ul><li><strong>Exchange</strong></li></ul><p>Exchange接口表示一个AMQP Exchange，这是消息生产者发送消息的对象。代理的虚拟主机中的每个Exchange都有唯一的名称以及其他一些属性。以下示例显示了Exchange接口：</p><pre><code class="language-java">public interface Exchange {    String getName();    String getExchangeType();    boolean isDurable();    boolean isAutoDelete();    Map&lt;String, Object&gt; getArguments();}</code></pre><p>Exchange有一个由ExchangeTypes定义的常量字段表示的类型。基本类型为：direct、 topic、 fanout和headers。在核心程序包中，可以找到每种类型的Exchange接口的实现。这些Exchange类型在处理队列绑定方面的行为各不相同。例如，direct exchange可让队列由固定的路由键（通常是队列名称）绑定。topic exchange支持具有路由模式的绑定，路由模式可能分别包括“*”（表示一个词）和“#”（表示多个词）通配符。 Fanout交换发布到与其绑定的所有队列，而不考虑任何路由键。</p><ul><li><strong>Queue</strong><br />Queue类代表消息消费者从中接收消息的组件。以下展示了Queue类：</li></ul><pre><code class="language-java">public class Queue  {    private final String name;    private volatile boolean durable;    private volatile boolean exclusive;    private volatile boolean autoDelete;    private volatile Map&lt;String, Object&gt; arguments;    /**     * The queue is durable, non-exclusive and non auto-delete.     *     * @param name the name of the queue.     */    public Queue(String name) {        this(name, true, false, false);    }    // Getters and Setters omitted for brevity}</code></pre><p>请注意，构造函数采用队列名称。管理模板(<strong>the admin template</strong>)可以提供用于生成唯一命名的队列的方法。这样的队列可以用作“答复”地址或在其他临时情况下使用。因此，自动生成的队列的“ exclusive”和“ autoDelete”属性都将设置为“ true”。</p><ul><li><strong>Binding</strong><br />假设生产者发送到exchange，而消费者从queue中接收，那么将queue连接到exchange的binding对这些生产者和消费者来说就至关重要。在Spring AMQP中，我们定义了一个Binding类来表示这些连接。<br />你可以用一个指定的路由键绑定一个queue和一个DirectExchange ，如下。</li></ul><pre><code class="language-java">new Binding(someQueue, someDirectExchange, &quot;foo.bar&quot;);</code></pre><p>你可以用一个指定的路由模式绑定一个queue和一个TopicExchange ，如下。</p><pre><code class="language-java">new Binding(someQueue, someTopicExchange, &quot;foo.*&quot;);</code></pre><p>你可以绑定一个queue和一个FanoutExchange ，而不指定路由键（因为这种是广播模式只要绑定就会转发），如下。</p><pre><code class="language-java">new Binding(someQueue, someFanoutExchange);</code></pre><p>我们也提供了一个 BindingBuilder 以便于使用 “fluent API”（流式API） 风格,如下:</p><pre><code class="language-java">Binding b = BindingBuilder.bind(someQueue).to(someTopicExchange).with(&quot;foo.*&quot;);</code></pre><p>就其本身而言，Binding类的实例仅保存有关连接的数据。换句话说，它不是“活动”组件。但是，如稍后在配置代理中所见，AmqpAdmin类可以使用Binding实例实际触发对代理的绑定操作。</p><p>就实际使用而言，其实应该基本上不会直接使用这几个类了，spring提供了更高级的API供我们使用，这里的说明旨在提供一个对Spring AMQP模型的抽象。</p>]]>
                    </description>
                    <pubDate>2019-12-04T21:38:08+08:00</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[ACL权限]]>
                    </title>
                    <link>https://seansun.xyz/archives/acl权限</link>
                    <description>
                            <![CDATA[<p>ACL主要是针对常规 <strong>linux文件系统rwx权限不够时使用的</strong>，centos6的时候可能需要在/etc/fstab下开启，centos7是默认开启的。</p><p><strong>getfacl</strong> + 文件、文件夹     查看acl权限</p><p><strong>setfacl</strong>  设置acl权限</p><ol><li>设置某用户某文件的acl权限</li></ol><p>setfacl -u u:username:rx 文件名</p><ol start="2"><li>设置某文件夹某用户组的acl权限</li></ol><p>setfacl -g g:groupname:rx 文件名</p><ol start="3"><li>最大权限</li></ol><p>setfacl -m m:rx 文件文件名</p><ol start="4"><li>删除某一用户或用户组的权限</li></ol><p>setfacl -x g:groupname  文件名</p><ol start="5"><li>删除所有acl权限</li></ol><p>setfacl -b 文件名</p><ol start="6"><li>递归权限</li></ol><p>setfacl -m u:用户名:权限 -R 文件名</p><ol start="7"><li>设置目录默认的acl权限</li></ol><p>setfacl -m d:u:用户名:权限 文件名</p>]]>
                    </description>
                    <pubDate>2019-11-27T21:29:29+08:00</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Linux常用命令]]>
                    </title>
                    <link>https://seansun.xyz/archives/linux常见命令</link>
                    <description>
                            <![CDATA[<h2 id="目录处理命令">目录处理命令</h2><p><strong>ls 命令</strong><br />-a   查看隐藏文件<br />-l   详细信息<br />-d   指定目录<br />-h   人性化显示文件大小<br />-i   i节点查看</p><p><strong>mkdir</strong> 创建新目录<br />-p 递归创建</p><p><strong>cd</strong>  切换目录</p><p><strong>pwd</strong>  查看当前目录</p><p><strong>rmdir</strong> 删除空目录</p><p><strong>cp</strong> 复制文件<br />-r 复制目录<br />-p 复制保持属性</p><p><strong>mv</strong>  剪切文件或者改名文件</p><p><strong>rm</strong> 删除文件</p><p>-r 删除目录</p><p>-f 不用询问确认</p><hr /><h2 id="文件处理命令">文件处理命令</h2><p><strong>touch</strong>  创建文件</p><blockquote><p>不建议使用空格</p></blockquote><p><strong>cat</strong> 查看文件内容</p><p>-n 加上行号</p><p><strong>tac</strong> 反向显示文件内容</p><blockquote><p>无-n选项</p></blockquote><p><strong>more</strong>  分页显示</p><ul><li>空格、f  翻页</li><li>回车 换行</li><li>q 推出</li></ul><p><strong>less</strong> 分页显示</p><ul><li>pageup 上翻页</li><li>上箭头 上换行</li><li>/搜索关键字  遍历搜索</li><li>n 下一个关键字</li></ul><p><strong>head</strong>  查看前几行</p><p>-n 查看前n行</p><p><strong>tail</strong>  查看最后几行</p><p>-n 查看最后n行</p><p>-f 动态显示添加的内容</p><hr /><h2 id="链接命令">链接命令</h2><p><strong>ln</strong> 生成硬链接</p><p>-s 生成软链接文件</p><blockquote><p>软链接文件特征：</p><ol><li>文件权限lrwxrwxrwx</li><li>文件大小-只是符号链接</li><li>普通复制只会复制原文件</li></ol><p>硬链接特征：</p><ol><li>相当于 cp -p + 同步跟新</li><li>通过 i节点 识别</li><li>不能跨分区</li><li>不能对目录使用</li></ol></blockquote><hr /><h2 id="权限管理命令">权限管理命令</h2><p><strong>chmod</strong> 更改权限</p><blockquote><p>操作符 + - =</p><p>数值   r=4  w=2  x=1</p><p>目录的写权限决定了目录下面的文件能否删除</p></blockquote><p><strong>chown 更改文件所有者</strong></p><blockquote><p>只有root有资格</p></blockquote><p><strong>chgrp</strong></p><p><strong>umask 默认权限掩码</strong></p><blockquote><p>默认值 022</p><p>文件默认还要去除执行文件</p></blockquote><hr /><h2 id="文件搜索命令">文件搜索命令</h2><p><strong>find</strong> 查找文件</p><p>-name 按照文件名查找</p><p>-iname 按文件名查找 不区分大小写</p><blockquote><p>通配符 ？ *</p></blockquote><p>-size</p><blockquote><p>大于多少</p><p>小于多少</p></blockquote><p>-user 根据用户查找</p><p>-group 根据用户组查找</p><p>-amin 根据访问时间查找</p><p>-mmin 根据内容修改时间查找</p><p>-cmin 根据属性修改时间查找</p><p>-type  根据文件类型来查找</p><blockquote><p>f 文件</p><p>d 目录</p><p>l 连接</p></blockquote><p>-inum 根据i节点来查找</p><p>-a AND</p><p>-o OR</p><p><strong>locate</strong>  快速查找</p><p>-i 不区分大小写</p><p>-u 更新资料库</p><blockquote><p>存在tmp还有一些目录文件不收录，也就检索不到</p></blockquote><p><strong>which</strong>  查找命令的绝对路径</p><blockquote><p>可以查找到文件别名</p></blockquote><p><strong>whereis</strong>  查找命令的绝对路径</p><blockquote><p>可以查找到帮助文档的位置</p></blockquote><p><strong>grep</strong> 筛选文件内容</p><p>-i 不区分大小写</p><p>-v 排除查找</p><hr /><h2 id="帮助命令">帮助命令</h2><p><strong>man</strong> 命令或者配置文件（只要文件名不要路径）</p><blockquote><p>命令    NAME 文件作用           通过/选项来查找选项的作用</p><p>配置文件    NAME 存放信息           文件格式</p></blockquote><p><strong>whatis</strong>  命令   查看命令简短介绍</p><p><strong>apropos</strong>  配置文件   配置文件相关信息</p><p>命令 --help  查看命令选项的信息</p><p><strong>help</strong> 内置命令  查看shell内置命令的帮助</p><hr /><h2 id="用户管理命令">用户管理命令</h2><p><strong>useradd</strong> 添加用户</p><p><strong>passwd</strong> 设置或者更改用户密码</p><p><strong>who</strong> 当前登录用户信息</p><p><strong>w</strong> 当前登录用户详细的信息</p><hr /><h2 id="压缩解压命令">压缩解压命令</h2><p><strong>.gz  格式</strong></p><p>压缩  gzip</p><p>解压  gunzip        gzip  -d</p><blockquote><p>不能压缩目录</p><p>不能保留原文件</p></blockquote><p><strong>.tar格式</strong></p><p>压缩  tar -cf</p><p>解压 tar -xf</p><p><strong>.tar.gz格式</strong></p><p>压缩  tar -zcf</p><p>解压  tar -zxf</p><p><strong>.zip格式</strong></p><p>压缩  zip</p><p>-r 压缩目录</p><p>解压  unzip</p><p><strong>.bz2格式</strong></p><p>压缩  bzip2</p><p>解压 bunzip2</p><p><strong>.tar.bz2格式</strong></p><p>tar -cjf</p><p>tar -xjf</p><hr /><h2 id="网络命令">网络命令</h2><p><strong>write  用户名</strong>   给用户发信息</p><blockquote><p>ctrl + d 结束</p><p>用户必须先登录</p></blockquote><p><strong>wall</strong> 内容   给所有用户发信息</p><p><strong>ping host</strong> 测试网络连通性</p><p>-c 次数   ping 指定的次数</p><p><strong>ifconfig</strong> 查看设置网卡信息</p><blockquote><p>ifconfig eth33 ip   临时设置 eth33网卡的ip</p><p>目前centos 7 用nmtui 来设置永久生效的网卡信息</p></blockquote><p><strong>mail 用户名</strong> 给本机用户离线发送邮件</p><blockquote><p>mail  进入邮件应用</p><p>help 帮助命令</p><p>邮件编号i   查看编号为i的邮件内容</p><p>h  邮件列表</p><p>d 邮件编号i  删除邮件编号为i的邮件</p><p>q 推出邮件应用</p></blockquote><p><strong>last</strong>  所有登录过的用户的登录信息</p><p><strong>lastlog</strong> 所有用户的最后一次登录的信息</p><p>-u  uid   uid用户的最后一次登录信息</p><p><strong>traceroute</strong> 显示数据包到主机间的路径</p><blockquote><p>使用方式 traceroute host</p></blockquote><p><strong>netstat</strong> 显示网络状态相关信息</p><p>-t tcp协议</p><p>-u udp协议</p><p>-l 监听</p><p>-r 路由</p><p>-n 显示ip和端口号</p><blockquote><p>netstat -tlun 查看本机监听的端口</p><p>netstat -an   查看本机所有网络连接</p><p>netstat -rn    查看本机路由表</p></blockquote><p><strong>setup</strong> 设置网络、防火墙、系统服务、验证服务等</p><blockquote><p>感觉已经被nmtui命令替代</p><p>网络设置完成可能需要重启网络服务</p></blockquote><p><strong>mount  -t  文件系统  设备文件名  挂载点</strong></p><blockquote><p>示例    mount -t iso9660 /dev/sr0 /mnt/cdrom</p></blockquote><p><strong>umount 卸载设备</strong></p><blockquote><p>不能在设备内卸载设备</p></blockquote><hr /><h2 id="关机重启命令"><strong>关机重启命令</strong></h2><p><strong>shutdown</strong>  选项  时间</p><p>-c 取消前一个关机命令</p><p>-h 关机</p><p>-r  重启</p><h3 id="关机命令">关机命令</h3><p><strong>halt</strong></p><p><strong>poweroff</strong></p><p><strong>init 0</strong></p><h3 id="重启命令"><strong>重启命令</strong></h3><p><strong>reboot</strong></p><p><strong>init 6</strong></p><p><strong>init</strong></p><blockquote><p>不允许设置0和6的启动模式</p><p>通过 <strong>runlevel</strong> 可以查询运行级别</p></blockquote><p><strong>logout</strong> 退出登录用户</p>]]>
                    </description>
                    <pubDate>2019-11-24T23:17:02+08:00</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[GNU's Not Unix! 搭个博客]]>
                    </title>
                    <link>https://seansun.xyz/archives/最近开始写文章</link>
                    <description>
                            <![CDATA[<p>标题是IT圈的一个老梗了，意思是“GNU（<a href="https://www.gnu.org/">一个自由软件操作系统</a>）不是Unix”，然后首字母前缀又组成了GNU一词。</p><p>笔者认为这个梗的精髓在于递归性质的自包含，所以第一篇文章的位置就让给了要怎么来快速构建这么一个博客。</p><ol><li>证书部分大体流程参考下面这么一篇文章:<br /><a href="https://medium.com/@pentacent/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71">https://medium.com/@pentacent/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71</a></li><li>博客部分选择了halo，配置好nginx的设置，代理halo，然后将halo的创建过程也在docker-compose文件中配置好，即完成了该博客的搭建。</li></ol><blockquote><p>最好注意一下docker-compose文件中的nginx的依赖配置，因为nginx反向代理halo的应用，所以nginx应该在halo启动之后启动。</p></blockquote><p>哒哒哒哒！大工搞成，</p>]]>
                    </description>
                    <pubDate>2019-10-26T18:20:51+08:00</pubDate>
                </item>
    </channel>
</rss>