<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>欲說還休</title><link>https://blog.terryx.com/categories/java-web/</link><description>TerryX's Blog</description><generator>Hugo 0.161.1</generator><language>zh-cn</language><managingEditor>TerryX</managingEditor><lastBuildDate>Wed, 21 Mar 2018 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.terryx.com/categories/java-web/index.xml" rel="self" type="application/rss+xml"/><item><title>Java Web 系统总结 (一)</title><link>https://blog.terryx.com/posts/2016-02-16-java-web-summary-1/</link><pubDate>Wed, 28 Sep 2016 00:00:00 +0000</pubDate><guid>https://blog.terryx.com/posts/2016-02-16-java-web-summary-1/</guid><description><![CDATA[<h3 id="一-jsp-总结">一. JSP 总结</h3>
<h4 id="9个隐含对象及其常用方法-虽然为-java-代码-但是使用-el-表达式完全可以进行调用相关-get-方法">9个隐含对象及其常用方法 (虽然为 Java 代码, 但是使用 <em>EL</em> 表达式完全可以进行调用相关 get 方法):</h4>
<p><strong>1. <em>Request (HttpServletRequest)</em></strong></p>
<ul>
<li><strong>Attribute</strong> 相关:</li>
</ul>
<p>可以理解在 <code>request</code> 作用域范围内的一个存储空间, 实现方法是将键值对放入一个 <code>HashMap</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Object</span><span class="w"> </span><span class="nf">getAttribute</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kt">void</span><span class="w"> </span><span class="nf">setAttribute</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="n">o</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kt">void</span><span class="w"> </span><span class="nf">removeAttribute</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><strong>Paramater</strong> 相关:</li>
</ul>
<p>一般用于与 <code>html</code>, <code>jsp</code>页面 及 <code>url</code> 地址传递参数, 仅能传递字符串. 注意: 没有 <code>setParamater</code> 方法.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="nf">getParameter</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="nf">getParameterValues</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><strong>Cookie</strong> 相关:</li>
</ul>
<p>作为浏览器端持久化储存的一种实现方法, 由服务器加入 <code>response</code> 中, 并在 <code>request</code> 中取出.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Cookie</span><span class="o">[]</span><span class="w"> </span><span class="nf">getCookies</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><strong>Session</strong> 相关:</li>
</ul>
<p>作为服务器持久化储存的一种实现方法, 在浏览器第一次访问服务器资源时自动创建.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">HttpSession</span><span class="w"> </span><span class="nf">getSession</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><strong>URI</strong> 路径:</li>
</ul>
<p><strong>应用名称 + 页面路径</strong></p>
<p>比如: <code>/day_15/bbs/bbs.jsp</code> .</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="nf">getRequestURI</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><strong>URL</strong> 路径:</li>
</ul>
<p><strong>站点地址 + URI路径</strong></p>
<p>比如: <code>http://localhost:8080/day_15/bbs/bbs.jsp</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">StringBuffer</span><span class="w"> </span><span class="nf">getRequestURL</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><strong>ServletPath</strong> 路径:</li>
</ul>
<p><strong>页面路径</strong></p>
<p>一般用于 <code>filter</code> 进行页面筛选. 比如: <code>/bbs/bbs.jsp</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="nf">getServletPath</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><ul>
<li>转发地址:</li>
</ul>
<p>可以将 <code>request</code> 转发到另一页面或 <code>Servlet</code>. 注意: <code>path</code> 为 <code>Servlet context path</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">RequestDispatcher</span><span class="w"> </span><span class="nf">getRequestDispatcher</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">path</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><ul>
<li>编码问题:</li>
</ul>
<p>对于携带中文作为 <code>Paramater</code> 的 <code>request</code> 在打印时会出现乱码, 所以需要进行编码.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">void</span><span class="w"> </span><span class="nf">setCharacterEncoding</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">env</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p><strong>2. <em>Response (HttpServletResponse)</em></strong></p>
<ul>
<li><strong>Cookie</strong> 相关:</li>
</ul>
<p>作为浏览器端持久化储存的一种实现方法, 由服务器加入 <code>response</code> 中, 并在 <code>request</code> 中取出.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">void</span><span class="w"> </span><span class="nf">addCookie</span><span class="p">(</span><span class="n">Cookie</span><span class="w"> </span><span class="n">cookie</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></div><ul>
<li>重定向地址</li>
</ul>
<p>可以跳转到另一页面, 但不转发 <code>request</code>. <code>location</code> 必须包含 <code>application context path</code> 或为 <code>URL</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">void</span><span class="w"> </span><span class="nf">sendRedirect</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">location</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p><strong>3. <em>Session (HttpSession)</em></strong></p>
<ul>
<li><strong>Attribute</strong> 相关:</li>
</ul>
<p>和 <code>request</code> 类似, <code>session</code> 也可以携带键值对, 使用方法完全类似.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Object</span><span class="w"> </span><span class="nf">getAttribute</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kt">void</span><span class="w"> </span><span class="nf">setAttribute</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="n">value</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kt">void</span><span class="w"> </span><span class="nf">removeAttribute</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><ul>
<li>设置生命周期:</li>
</ul>
<p>从创建到销毁的时间.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">void</span><span class="w"> </span><span class="nf">setMaxInactiveInterval</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">interval</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kt">int</span><span class="w"> </span><span class="nf">getMaxInactiveInterval</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><p>立即销毁 <code>session</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">void</span><span class="w"> </span><span class="nf">invalidate</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><strong>JSessionId</strong>:</li>
</ul>
<p>以 <code>JSessionId</code> 为名称存入 <code>Cookie</code> 中.</p>
<p>获取 <code>JSessionId</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="nf">getId</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><ul>
<li>时间信息:</li>
</ul>
<p>获取创建与最近访问时间:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">long</span><span class="w"> </span><span class="nf">getCreationTime</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kt">long</span><span class="w"> </span><span class="nf">getLastAccessedTime</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><ul>
<li>获取 <strong>ServletContext</strong></li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">ServletContext</span><span class="w"> </span><span class="nf">getServletContext</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><p>以上总结的三个隐含对象 <code>request</code> <code>response</code> <code>session</code>, 不只是对于 <code>JSP</code> , 而在整个 <code>Java Web</code> 技术中都极为重要, 对其常用的实现方法必须使用熟练.</p>
<p><strong>4. <em>Application (ServletContext)</em></strong></p>
<ul>
<li><strong>Attribute</strong> 相关:</li>
</ul>
<p>和 <code>request</code> <code>session</code> 类似, <code>Servlet</code> 也可以携带键值对, 使用方法完全类似.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Object</span><span class="w"> </span><span class="nf">getAttribute</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kt">void</span><span class="w"> </span><span class="nf">setAttribute</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="n">value</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kt">void</span><span class="w"> </span><span class="nf">removeAttribute</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><strong>Paramater</strong> 相关:</li>
</ul>
<p>获取在 <code>web.xml</code> 中设置的 <code>Servlet</code> 初始化参数.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="nf">getInitParameter</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><strong>Servlet Context Path</strong></li>
</ul>
<p>为服务器应用的路径名, 如: <code>/day_15</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="nf">getContextPath</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><p>仅获取应用名称,  如: <code>day_15</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="nf">getServletContextName</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><p><strong>5. <em>Config (ServletConfig)</em></strong></p>
<p><code>ServletContext</code> 可以覆盖其实现的所有功能.</p>
<ul>
<li><strong>Paramater</strong> 相关:</li>
</ul>
<p>获取在 <code>web.xml</code> 中设置的 <code>Servlet</code> 初始化参数.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="nf">getInitParameter</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><ul>
<li>获取 <strong>ServletContext</strong>:</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">ServletContext</span><span class="w"> </span><span class="nf">getServletContext</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><ul>
<li>获取应用名称:</li>
</ul>
<p>如: <code>day_15</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="nf">getServletContextName</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><p><strong>6. <em>PageContext (PageContext)</em></strong></p>
<ul>
<li>可以获取其他所有隐藏对象 <strong>Request Response Session ServletContext ServletConfig Out Page Exception</strong>:</li>
</ul>
<p>直接调用 <code>get</code> 方法既可. 因为可以调用所有功能, 多用于 <code>EL</code> 表达式.</p>
<p><strong>7. 其余不常用的三个: Out Page Exception</strong>:</p>
<p>以上9个隐藏对象包含 <code>Java Web</code> 最重要的四个作用域:</p>
<ul>
<li><strong>Application</strong>: 作用于整个 Web 应用, 生命周期为 <strong>应用的添加到移除</strong>.</li>
<li><strong>Session</strong>: 作用于一次会话, 生命周期为 <strong>浏览器第一次访问生成到生命周期结束自动注销</strong>.</li>
<li><strong>Request</strong>: 作用于一次请求, 生命周期为 <strong>发送请求到请求处理结束</strong>.</li>
<li><strong>Page</strong>: 仅作用于本页面, 无生命周期.</li>
</ul>
]]></description></item><item><title>深入理解 HTTP Session</title><link>https://blog.terryx.com/posts/2016-02-17-http-session-summary/</link><pubDate>Wed, 28 Sep 2016 00:00:00 +0000</pubDate><guid>https://blog.terryx.com/posts/2016-02-17-http-session-summary/</guid><description><![CDATA[<blockquote>
<p>转载于 <a href="http://lavasoft.blog.51cto.com/62575/275589">深入理解 HTTP Session</a></p>
</blockquote>
<p>Session 在 web 开发中是一个非常重要的概念, 这个概念很抽象,很难定义,也是最让人迷惑的一个名词, 也是最多被滥用的名字之一, 在不同的场合,session一次的含义也很不相同. 这里只探讨 HTTP Session.</p>
<p>为了说明问题,这里基于 Java Servlet 理解 Session 的概念与原理,这里所说 Servlet 已经涵盖了 JSP 技术,因为 JSP 最终也会被编译为 Servlet,两者有着相同的本质.</p>
<p>在 Java 中, HTTP 的 Session 对象用 <code>javax.servlet.http.HttpSession</code> 来表示.</p>
<ol>
<li>概念: Session 代表服务器与浏览器的一次会话过程, 这个过程是连续的, 也可以时断时续的. 在 Servlet 中, session 指的是 HttpSession 类的对象, 这个概念到此结束了, 也许会很模糊, 但只有看完本文,才能真正有个深刻理解.</li>
<li>Session 创建的时间是:</li>
</ol>
<p>一个常见的误解是以为 session 在有客户端访问时就被创建, 然而事实是直到某 server 端程序调用 <code>HttpServletRequest.getSession(true)</code> 这样的语句时才被创建,注意如果JSP没有显示的使用 <code>&lt;% @page session=&quot;false&quot;%&gt;</code> 关闭 session, <strong>则 JSP 文件在编译成 Servlet 时将会自动加上这样一条语句 <code>HttpSession session = HttpServletRequest.getSession(true)</code>; 这也是JSP中隐含的 session对象的来历.</strong></p>
<p>由于 session 会消耗内存资源, 因此, 如果不打算使用 session, 应该在所有的 JSP 中关闭它.</p>
<p>引申:</p>
<ul>
<li>访问 <code>*.html</code> 的静态资源因为不会被编译为 Servlet, 也就不涉及 session 的问题.</li>
<li>当 JSP 页面没有显式禁止 session 的时候, 在打开浏览器第一次请求该 jsp 的时候, 服务器会自动为其创建一个 session, 并赋予其一个 sessionID, 发送给客户端的浏览器. 以后客户端接着请求本应用中其他资源的时候, 会自动在请求头上添加:
<code>Cookie:JSESSIONID</code> = 客户端第一次拿到的 session ID
这样, 服务器端在接到请求时候, 就会收到 session ID, 并根据 ID 在内存中找到之前创建的 session 对象, 提供给请求使用. 这也是 session 使用的基本原理 —- 搞不懂这个, 就永远不明白session的原理.</li>
</ul>
<ol start="3">
<li>Session删除的时间是:</li>
</ol>
<ul>
<li>Session 超时: 超时指的是连续一定时间服务器没有收到该 Session 所对应客户端的请求,并且这个时间超过了服务器设置的 Session 超时的最大时间.</li>
<li>程序调用 <code>HttpSession.invalidate()</code></li>
<li>服务器关闭或服务停止</li>
</ul>
<ol start="4">
<li>session 存放在哪里: 服务器端的内存中. 不过 session 可以通过特殊的方式做持久化管理.</li>
<li>session 的 id 是从哪里来的, sessionID 是如何使用的: 当客户端第一次请求 session 对象时候, 服务器会为客户端创建一个 session, 并将通过特殊算法算出一个 session 的 ID, 用来标识该session对象, 当浏览器下次 (session 继续有效时) 请求别的资源的时候, 浏览器会偷偷地将sessionID放置到请求头中, 服务器接收到请求后就得到该请求的 sessionID, 服务器找到该 id 的 session 返还给请求者 (Servlet) 使用. 一个会话只能有一个 session 对象, <strong>对 session 来说是只认 id 不认人</strong>.</li>
<li>session 会因为浏览器的关闭而删除吗？</li>
</ol>
<p>不会, session 只会通过上面提到的方式去关闭.
7. 同一客户端机器多次请求同一个资源, session 一样吗？</p>
<p>一般来说,每次请求都会新创建一个 session.</p>
<p>其实, 这个也不一定的, 总结下: <strong>对于多标签的浏览器 (比如 Chrome 浏览器) 来说, 在一个浏览器窗口中, 多个标签同时访问一个页面, session是一个. 对于多个浏览器窗口之间, 同时或者相隔很短时间访问一个页面, session是多个的, 和浏览器的进程有关. 对于一个同一个浏览器窗口, 直接录入url访问同一应用的不同资源, session是一样的.</strong>
8. session 是一个容器,可以存放会话过程中的任何对象.
9. session 因为请求 (request对象) 而产生, 同一个会话中多个 request 共享了一个 session 对象, 可以直接从请求中获取到 session 对象.
10. <strong>其实, session 的创建和使用总在服务端, 而浏览器从来都没得到过 session 对象.</strong> 但浏览器可以请求 Servlet (jsp 也是 Servlet) 来获取 session 的信息. 客户端浏览器真正紧紧拿到的是 session ID, 而这个对于浏览器操作的人来说, 是不可见的, 并且用户也无需关心自己处于哪个会话过程中.</p>
]]></description></item><item><title>用 Redis 实现分布式锁 与 实现任务队列</title><link>https://blog.terryx.com/posts/2016-04-27-redis/</link><pubDate>Wed, 28 Sep 2016 00:00:00 +0000</pubDate><guid>https://blog.terryx.com/posts/2016-04-27-redis/</guid><description><![CDATA[<blockquote>
<p>转载于 <a href="http://www.cnblogs.com/it-cen/p/4984272.html">用 Redis 实现分布式锁 与 实现任务队列</a></p>
</blockquote>
<p>这一次总结和分享用Redis实现分布式锁 与 实现任务队列 这两大强大的功能. 先扯点个人观点, 之前我看了一篇博文说博客园的文章大部分都是分享代码, 博文里强调说分享思路比分享代码更重要 (貌似大概是这个意思, 若有误请谅解) , 但我觉得, 分享思路固然重要, 但有了思路, 却没有实现的代码, 那会让人觉得很浮夸的, 在工作中的程序猿都知道, 你去实现一个功能模块, 一段代码, 虽然你有了思路, 但是实现的过程也是很耗时的, 特别是代码调试, 还有各种测试等等. 所以我认为, 思路+代码, 才是一篇好博文的主要核心.</p>
<p>直接进入主题.</p>
<h3 id="一前言">一、前言</h3>
<p>双十一刚过不久, 大家都知道在天猫、京东、苏宁等等电商网站上有很多秒杀活动, 例如在某一个时刻抢购一个原价1999现在秒杀价只要999的手机时, 会迎来一个用户请求的高峰期, 可能会有几十万几百万的并发量, 来抢这个手机, 在高并发的情形下会对数据库服务器或者是文件服务器应用服务器造成巨大的压力, 严重时说不定就宕机了, 另一个问题是, 秒杀的东西都是有量的, 例如一款手机只有10台的量秒杀, 那么, 在高并发的情况下, 成千上万条数据更新数据库 (例如10台的量被人抢一台就会在数据集某些记录下 减1) , 那次这个时候的先后顺序是很乱的, 很容易出现10台的量, 抢到的人就不止10个这种严重的问题. 那么, 以后所说的问题我们该如何去解决呢？ 接下来我所分享的技术就可以拿来处理以上的问题： 分布式锁 和 任务队列.</p>
<h3 id="二实现思路">二、实现思路</h3>
<h4 id="1redis-实现分布式锁思路">1.Redis 实现分布式锁思路</h4>
<p>思路很简单, 主要用到的 <code>redis</code> 函数是 <code>setnx()</code> , 这个应该是实现分布式锁最主要的函数. 首先是将某一任务标识名 (这里用 <code>Lock:order</code> 作为标识名的例子) 作为键存到 <code>redis</code> 里, 并为其设个过期时间, 如果是还有 <code>Lock:order</code> 请求过来, 先是通过 <code>setnx()</code> 看看是否能将 <code>Lock:order</code> 插入到 redis 里, 可以的话就返回 true, 不可以就返回 false. 当然, 在我的代码里会比这个思路复杂一些, 我会在分析代码时进一步说明.</p>
<h4 id="2redis-实现任务队列">2.Redis 实现任务队列</h4>
<p>这里的实现会用到上面的 <code>Redis</code> 分布式的锁机制, 主要是用到了<code>Redis</code> 里的有序集合这一数据结构. 例如入队时, 通过 <code>zset</code> 的 <code>add()</code> 函数进行入队, 而出对时, 可以用到 <code>zset</code> 的 <code>getScore()</code> 函数. 另外还可以弹出顶部的几个任务.</p>
<p>以上就是实现 分布式锁 和 任务队列 的简单思路, 如果你看完有点模棱两可, 那请看接下来的代码实现.</p>
<h3 id="三代码分析">三、代码分析</h3>
<h4 id="一-先来分析redis分布式锁的代码实现">(一) 先来分析Redis分布式锁的代码实现</h4>
<ul>
<li>(1) 为避免特殊原因导致锁无法释放, 在加锁成功后, 锁会被赋予一个生存时间 (通过 <code>lock</code>方法的参数设置或者使用默认值) , 超出生存时间锁会被自动释放锁的生存时间默认比较短 (秒级) , 因此, 若需要长时间加锁, 可以通过 <code>expire</code> 方法延长锁的生存时间为适当时间, 比如在循环内.</li>
<li>(2) 系统级的锁当进程无论何种原因时出现 crash 时, 操作系统会自己回收锁, 所以不会出现资源丢失, 但分布式锁不用, 若一次性设置很长时间, 一旦由于各种原因出现进程crash 或者其他异常导致unlock未被调用时, 则该锁在剩下的时间就会变成垃圾锁, 导致其他进程或者进程重启后无法进入加锁区域.</li>
</ul>
<p>先看加锁的实现代码：这里需要主要两个参数, 一个是 <code>$timeout</code>,这个是循环获取锁的等待时间, 在这个时间内会一直尝试获取锁知道超时, 如果为 0, 则表示获取锁失败后直接返回而不再等待；另一个重要参数的 <code>$expire</code>, 这个参数指当前锁的最大生存时间, 以秒为单位的, 它必须大于0, 如果超过生存时间锁仍未被释放, 则系统会自动强制释放. 这个参数的最要作用请看上面的 (1) 里的解释.</p>
<p>这里先取得当前时间, 然后再获取到锁失败时的等待超时的时刻 (是个时间戳), 再获取到锁的最大生存时刻是多少. 这里 redis 的 key 用这种格式：<code>Lock: 锁的标识名</code>, 这里就开始进入循环了, 先是插入数据到redis里, 使用 <code>setnx()</code> 函数, 这函数的意思是, 如果该键不存在则插入数据, 将最大生存时刻作为值存储, 假如插入成功, 则对该键进行失效时间的设置, 并将该键放在 <code>$lockedName</code> 数组里, 返回 true, 也就是上锁成功；如果该键存在, 则不会插入操作了, 这里有一步严谨的操作, 那就是取得当前键的剩余时间, 假如这个时间小于 0, 表示 key 上没有设置生存时间 (key是不会不存在的, 因为前面setnx会自动创建) 如果出现这种状况, 那就是进程的某个实例 setnx 成功后 crash 导致紧跟着的 expire 没有被调用, 这时可以直接设置expire并把锁纳为己用. 如果没设置锁失败的等待时间或者已超过最大等待时间了, 那就退出循环, 反之则隔 <code>$waitIntervalUs</code> 后继续请求. 这就是加锁的整一个代码分析.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 加锁
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @param  [type]  $name           锁的标识名
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @param  integer $timeout        循环获取锁的等待超时时间，在此时间内会一直尝试获取锁直到超时，为 0表示失败后直接返回不等待
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @param  integer $expire         当前锁的最大生存时间(秒)，必须大于 0，如果超过生存时间锁仍未被释放，则系统会自动强制释放
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @param  integer $waitIntervalUs 获取锁失败后挂起再试的时间间隔(微秒)
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @return [type]                  [description]
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="nf">lock</span><span class="p">(</span><span class="n">$name</span><span class="p">,</span><span class="w"> </span><span class="n">$timeout</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">0</span><span class="p">,</span><span class="w"> </span><span class="n">$expire</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">15</span><span class="p">,</span><span class="w"> </span><span class="n">$waitIntervalUs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">100000</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">$name</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">null</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">//取得当前时间</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">$now</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">time</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">//获取锁失败时的等待超时时刻</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">$timeoutAt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">$now</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">$timeout</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">//锁的最大生存时刻</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">$expireAt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">$now</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">$expire</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">$redisKey</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;Lock:{$name}&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">//将rediskey的最大生存时刻存到redis里，过了这个时刻该锁会被自动释放</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">$this</span><span class="o">-&gt;</span><span class="n">redisString</span><span class="o">-&gt;</span><span class="n">setnx</span><span class="p">(</span><span class="n">$redisKey</span><span class="p">,</span><span class="w"> </span><span class="n">$expireAt</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">$result</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">false</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">//设置key的失效时间</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">$this</span><span class="o">-&gt;</span><span class="n">redisString</span><span class="o">-&gt;</span><span class="n">expire</span><span class="p">(</span><span class="n">$redisKey</span><span class="p">,</span><span class="w"> </span><span class="n">$expireAt</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">//将锁标志放到lockedNames数组里</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">$this</span><span class="o">-&gt;</span><span class="n">lockedNames</span><span class="o">[</span><span class="n">$name</span><span class="o">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">$expireAt</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">//以秒为单位，返回给定key的剩余生存时间</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">$ttl</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">$this</span><span class="o">-&gt;</span><span class="n">redisString</span><span class="o">-&gt;</span><span class="n">ttl</span><span class="p">(</span><span class="n">$redisKey</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">//ttl小于0 表示key上没有设置生存时间（key是不会不存在的，因为前面setnx会自动创建）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">//如果出现这种状况，那就是进程的某个实例setnx成功后 crash 导致紧跟着的expire没有被调用</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">//这时可以直接设置expire并把锁纳为己用</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">$ttl</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">$this</span><span class="o">-&gt;</span><span class="n">redisString</span><span class="o">-&gt;</span><span class="n">set</span><span class="p">(</span><span class="n">$redisKey</span><span class="p">,</span><span class="w"> </span><span class="n">$expireAt</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">$this</span><span class="o">-&gt;</span><span class="n">lockedNames</span><span class="o">[</span><span class="n">$name</span><span class="o">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">$expireAt</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="cm">/*****循环请求锁部分*****/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">//如果没设置锁失败的等待时间 或者 已超过最大等待时间了，那就退出</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">$timeout</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="n">0</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">$timeoutAt</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">microtime</span><span class="p">(</span><span class="kc">true</span><span class="p">))</span><span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">//隔 $waitIntervalUs 后继续 请求</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">usleep</span><span class="p">(</span><span class="n">$waitIntervalUs</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div>]]></description></item></channel></rss>