Alwayswithme's Blog Doing all I can. To be a better man. https://alwayswithme.github.io/ Fri, 09 Jun 2017 01:54:25 +0000 Fri, 09 Jun 2017 01:54:25 +0000 Jekyll v3.4.3 分布式锁 <p>在分布式系统中,如多个实例或集群节点中,要有一种方式来保证同步共享资源的访问。此时编程语言提供的锁同步便无能为力,这时就需要用到分布式锁。分布式锁常用于:</p> <ol> <li>保证计算效率:通过锁互斥避免消耗大量资源的重复计算,具体场景:防止并发请求</li> <li>保证数据一致:保证同一份数据每次只有一个进程修改,具体场景:下单减库存</li> </ol> <h3 id="思路">思路</h3> <p>简单来说可以分为三步操作</p> <ol> <li>获取锁,此时有可能获取失败可加入重试或阻塞逻辑</li> <li>执行具体操作</li> <li>释放锁,此时要判断可能存在超时自动释放的逻辑</li> </ol> <h3 id="实现方式">实现方式</h3> <p>根据系统架构和技术栈的不同,实现方式可以分为:</p> <h4 id="数据库的乐观锁或排它锁">数据库的乐观锁或排它锁</h4> <p>乐观锁一般是指增加一个版本号,修改数据时每次都会更新版本号,保证只有一次修改。 排它锁,利用 <code class="highlighter-rouge">SELECT ... FROM ... FOR UPDATE</code> 的语法选择一行数据上排他锁,其他事务在执行这条语句会阻塞。另外,可以把数据库引擎改为 InnoDB 并用上唯一索引将锁优化为行锁。</p> <h4 id="缓存的原子性操作">缓存的原子性操作</h4> <p><a href="https://github.com/memcached/memcached">MemCache</a> 的 <a href="https://github.com/memcached/memcached/wiki/Commands#add"><code class="highlighter-rouge">ADD</code></a> 命令,这个命令的作用是只有 key 不存在时才添加,可以认为获得了锁。 <a href="https://github.com/antirez/redis">Redis</a> 的 <a href="https://redis.io/commands/set"><code class="highlighter-rouge">SET</code></a> 也类似,没有看错,Redis 2.6.12 之后的版本 <code class="highlighter-rouge">SET</code> 命令可以添加 NX 选项来替代 <code class="highlighter-rouge">SETNX</code> 。鉴于大量资料还是用 <code class="highlighter-rouge">SETNX</code>, <code class="highlighter-rouge">GETSET</code> 等命令组合来检查时间戳加锁,这里有必要提一下。两种方式分别在两个 Redis 命令文档的下面可以查阅。</p> <ol> <li>新的实现方式 <code class="highlighter-rouge">SET</code> 配合 Lua 脚本:https://redis.io/commands/set</li> <li>旧的实现方式 <code class="highlighter-rouge">SETNX</code>, <code class="highlighter-rouge">GET</code> 和 <code class="highlighter-rouge">GETSET</code>: https://redis.io/commands/setnx</li> </ol> <h4 id="zookeeper-临时节点">ZooKeeper 临时节点</h4> <p>基于 <a href="https://github.com/apache/zookeeper">ZooKeeper</a> 创建临时有序节点,锁可以认为是由创建最小序号的节点进程获取,释放锁删除创建的有序临时节点即可。</p> <h3 id="利用-memcache-和-spring-aop-的简单实现">利用 MemCache 和 Spring AOP 的简单实现</h3> <p>先写一个简单的类,这个类是无状态的,很适合作为Spring 的 Bean。可以看作是一个 LockService</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MemCacheLock</span> <span class="o">{</span> <span class="kd">private</span> <span class="n">MemCachedClient</span> <span class="n">client</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">MemCacheLock</span><span class="o">(</span><span class="n">MemCachedClient</span> <span class="n">client</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">client</span> <span class="o">=</span> <span class="n">client</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * 通过 add(lockKey, token) 获取锁 * * @param lockKey 上锁的 key * @param retry 重试次数 * @param expireSec 过期时间,单位秒 * @param token key 对应的 value, 删除时比对避免释放其他客户端的锁 * @return */</span> <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">acquire</span><span class="o">(</span><span class="n">String</span> <span class="n">lockKey</span><span class="o">,</span> <span class="kt">int</span> <span class="n">retry</span><span class="o">,</span> <span class="kt">int</span> <span class="n">expireSec</span><span class="o">,</span> <span class="n">String</span> <span class="n">token</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// anyMatch 迭代 retry + 1 次,直到成功获取锁</span> <span class="k">return</span> <span class="n">IntStream</span><span class="o">.</span><span class="na">rangeClosed</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">retry</span><span class="o">).</span><span class="na">anyMatch</span><span class="o">((</span><span class="n">i</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="o">{</span> <span class="c1">// 第一次获取锁不等待,随后的重试等待 100 ms</span> <span class="k">try</span> <span class="o">{</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">MILLISECONDS</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="n">i</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">?</span> <span class="mi">100</span> <span class="o">:</span> <span class="mi">0</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="n">client</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">lockKey</span><span class="o">,</span> <span class="n">token</span><span class="o">,</span> <span class="k">new</span> <span class="n">Date</span><span class="o">(</span><span class="n">expireSec</span> <span class="o">*</span> <span class="mi">1000</span><span class="o">));</span> <span class="o">});</span> <span class="o">}</span> <span class="cm">/** * 通过 del(lockKey) 释放锁 * * @param lockKey 上锁的 key * @param token key 对应的 value * @return */</span> <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">release</span><span class="o">(</span><span class="n">String</span> <span class="n">lockKey</span><span class="o">,</span> <span class="n">String</span> <span class="n">token</span><span class="o">)</span> <span class="o">{</span> <span class="n">Object</span> <span class="n">obj</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">lockKey</span><span class="o">);</span> <span class="n">String</span> <span class="n">get</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="n">String</span><span class="o">)</span> <span class="o">{</span> <span class="n">get</span> <span class="o">=</span> <span class="o">(</span><span class="n">String</span><span class="o">)</span> <span class="n">obj</span><span class="o">;</span> <span class="o">}</span> <span class="c1">// 通过 token 保证不会释放其他客户端的锁</span> <span class="k">return</span> <span class="n">Objects</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">get</span><span class="o">,</span> <span class="n">token</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="n">client</span><span class="o">.</span><span class="na">delete</span><span class="o">(</span><span class="n">lockKey</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <p>因为 <code class="highlighter-rouge">acquire</code> 和 <code class="highlighter-rouge">release</code> 总是配合使用,可以利用模板方法在切面里应用,定义一个 annotation 和 aspect</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="nd">@Target</span><span class="o">({</span><span class="n">ElementType</span><span class="o">.</span><span class="na">METHOD</span><span class="o">})</span> <span class="nd">@Retention</span><span class="o">(</span><span class="n">RetentionPolicy</span><span class="o">.</span><span class="na">RUNTIME</span><span class="o">)</span> <span class="nd">@Documented</span> <span class="kd">public</span> <span class="nd">@interface</span> <span class="n">DistributedLock</span> <span class="o">{</span> <span class="cm">/** * 省略 default 要显示指定一个key */</span> <span class="n">String</span> <span class="nf">key</span><span class="o">();</span> <span class="kt">int</span> <span class="nf">expired</span><span class="o">()</span> <span class="k">default</span> <span class="mi">10</span><span class="o">;</span> <span class="kt">int</span> <span class="nf">acquireRetry</span><span class="o">()</span> <span class="k">default</span> <span class="mi">3</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Aspect</span> <span class="nd">@Component</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">DistributedLockAspect</span> <span class="o">{</span> <span class="nd">@Autowired</span> <span class="n">MemCacheLock</span> <span class="n">lockService</span><span class="o">;</span> <span class="nd">@Around</span><span class="o">(</span><span class="s">"@annotation(distributedLock)"</span><span class="o">)</span> <span class="kd">public</span> <span class="n">Object</span> <span class="nf">runWithLock</span><span class="o">(</span><span class="n">ProceedingJoinPoint</span> <span class="n">pjp</span><span class="o">,</span> <span class="n">DistributedLock</span> <span class="n">distributedLock</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Throwable</span> <span class="o">{</span> <span class="kt">int</span> <span class="n">expired</span> <span class="o">=</span> <span class="n">distributedLock</span><span class="o">.</span><span class="na">expired</span><span class="o">();</span> <span class="kt">int</span> <span class="n">retry</span> <span class="o">=</span> <span class="n">distributedLock</span><span class="o">.</span><span class="na">acquireRetry</span><span class="o">();</span> <span class="n">String</span> <span class="n">key</span> <span class="o">=</span> <span class="s">"dist_lock_"</span> <span class="o">+</span> <span class="n">distributedLock</span><span class="o">.</span><span class="na">key</span><span class="o">();</span> <span class="kt">long</span> <span class="n">l</span> <span class="o">=</span> <span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()</span> <span class="o">/</span> <span class="mi">1000</span><span class="o">)</span> <span class="o">+</span> <span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">hashCode</span><span class="o">();</span> <span class="n">String</span> <span class="n">token</span> <span class="o">=</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">l</span><span class="o">);</span> <span class="kt">boolean</span> <span class="n">acquired</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="n">acquired</span> <span class="o">=</span> <span class="n">lockService</span><span class="o">.</span><span class="na">acquire</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">retry</span><span class="o">,</span> <span class="n">expired</span><span class="o">,</span> <span class="n">token</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">acquired</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">pjp</span><span class="o">.</span><span class="na">proceed</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">acquired</span><span class="o">)</span> <span class="o">{</span> <span class="n">lockService</span><span class="o">.</span><span class="na">release</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">token</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="s">"can not acquire lock"</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <p>随后在 service 和 dao 方法中,标注上 <code class="highlighter-rouge">@DistributedLock(key="test")</code> 便能上锁,但是要注意这里如果无法获取锁会抛出异常</p> <h3 id="redlock">Redlock</h3> <p>Redis 官方提出的一种分布式锁算法 <a href="https://github.com/antirez/redis-doc/blob/master/topics/distlock.md">Redlock</a>,但可以在文档的<a href="https://github.com/antirez/redis-doc/blob/master/topics/distlock.md#analysis-of-redlock">最后</a>看到有一些有意思的讨论,分析 Redlock 是否靠谱。</p> <ul> <li>https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html</li> <li>http://antirez.com/news/101</li> </ul> <h3 id="其他更健壮的实现">其他更健壮的实现</h3> <ul> <li><a href="https://redisson.org/">Redisson</a>: 可以作为 Redis Java 客户端,提供了分布式锁和和同步器,API非常友好,中文 Wiki 也非常完善</li> <li><a href="https://curator.apache.org/">Curator</a>: 一个更高层抽象的 ZooKeeper 客户端,其中的 InterProcessMutex 便是分布式锁</li> </ul> <h3 id="相关链接">相关链接</h3> <ul> <li>http://www.hollischuang.com/archives/1716</li> <li>http://www.importnew.com/20307.html</li> <li>https://russellneufeld.wordpress.com/2012/05/24/using-memcached-as-a-distributed-lock-from-within-django/</li> <li>https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/chubby-osdi06.pdf</li> </ul> Fri, 09 Jun 2017 09:44:22 +0000 https://alwayswithme.github.io/java/distributed/2017/06/09/distributed-lock.html https://alwayswithme.github.io/java/distributed/2017/06/09/distributed-lock.html java distributed 译:Inside NGINX: How We Designed for Performance & Scale <p>NGINX 的官方博客有不少高质量的文章,其中一篇高屋建瓴的介绍了 NGINX 的架构。NGINX 的设计使用了 <a href="https://en.wikipedia.org/wiki/Reactor_pattern">Reactor 模式</a>, 下面提到的 master 和 worker 即所谓的 MainReactor 和 SubReactor, 意会即可。</p> <blockquote> <p>原文链接:<a href="https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale/">Inside NGINX: How We Designed for Performance &amp; Scale</a></p> </blockquote> <h2 id="深入-nginx-性能与拓展">深入 NGINX: 性能与拓展</h2> <p>NGINX 能在 web 性能上独树一帜归功于它软件的设计方式。鉴于大部分 web 服务器和应用服务器使用简单线程/进程架构, 精致的事件驱动模型使得 NGINX 脱颖而出, 令它能在现代的硬件上处理成千上万的并发连接。</p> <p>这份 <a href="http://www.nginx.com/resources/library/infographic-inside-nginx/">Inside NGINX</a> 信息图从高级的进程架构切入, 描述 NGINX 是如何以单一进程处理多个连接。 本文更详细地解释它是如何工作的。</p> <h3 id="好戏开场-nginx-进程模型">好戏开场, NGINX 进程模型</h3> <p><img src="https://cdn.wp.nginx.com/wp-content/uploads/2015/06/Screen-Shot-2015-06-08-at-12.36.30-PM.png" alt="Master Process" title="the NGINX master Process and Child Processes" /></p> <p>了解 NGINX 是如何运行的将有助于你更好理解这个设计。 NGINX 有一个 master 进程(负责执行像读取配置和绑定端口这样的权限操作) 和一系列 worker 进程和 helper 进程。</p> <div class="language-terminal highlighter-rouge"><pre class="highlight"><code><span class="ni"># </span><span class="nc">service</span><span class="kv"> nginx restart </span>* Restarting nginx <span class="ni"># </span><span class="nc">ps</span><span class="kv"> -ef --forest | grep nginx </span>root 32475 1 0 13:36 ? 00:00:00 nginx: master process /usr/sbin/nginx \ -c /etc/nginx/nginx.conf nginx 32476 32475 0 13:36 ? 00:00:00 \_ nginx: worker process nginx 32477 32475 0 13:36 ? 00:00:00 \_ nginx: worker process nginx 32479 32475 0 13:36 ? 00:00:00 \_ nginx: worker process nginx 32480 32475 0 13:36 ? 00:00:00 \_ nginx: worker process nginx 32481 32475 0 13:36 ? 00:00:00 \_ nginx: cache manager process nginx 32482 32475 0 13:36 ? 00:00:00 \_ nginx: cache loader process </code></pre> </div> <p>在这台四核服务器, NGINX 的 master 进程创建了四个 worker 进程和一系列缓存 helper 进程负责管理磁盘上的内容缓存。</p> <h3 id="为什么架构很重要">为什么架构很重要?</h3> <p>一切 Unix 程序的基本单元都是线程和进程。(从 Linux 操作系统来说, 线程和进程是大体相似的;主要区别在于它们共享内存的程度。) 一个线程或进程是一组由 CPU 调度在一个核芯上运行的独立指令集。 大部分复杂的程序并行运行多个线程或进程,原因有二:</p> <ul> <li>可以同时使用更多计算核芯</li> <li>可以让并行操作简单的进行(举例来说,同时处理多个连接)。</li> </ul> <p>进程和线程会消耗资源。它们都要用到内存和其他操作系统资源,而且调度时需要在核芯中装入和移出(一种叫上下文切换的操作)。多数现代的服务器可以同时处理数百个的小型,活跃的线程和进程,不过一旦内存用尽或者 I/O 负载很高导致大量的上下文切换性能将严重下降。</p> <p>网络程序的常用设计方式是为每一个连接分配一个线程或进程。这种架构很简单而且方便实现, 但难以拓展至处理成千上万的连接。</p> <ul> <li>译注: <a href="https://en.wikipedia.org/wiki/C10k_problem">C10K 问题</a></li> </ul> <h3 id="nginx-工作方式">NGINX 工作方式</h3> <p>NGINX 使用一种可预测的进程模型,并为可用硬件资源进行优化:</p> <ul> <li><em>master</em> 进程执行诸如读取配置和绑定端口等权限操作, 然后创建少量的子进程(下面介绍的三种类型)。</li> <li><em>cache loader</em> 进程在启动时把磁盘缓存载入内存, 然后退出。它被谨慎的调度所以资源需求很低。</li> <li><em>cache manager</em> 进程周期性的运行并清理磁盘缓存条目, 使其保持在配置指定的大小。</li> <li><em>worker</em> 进程负责其余工作, 包括处理网络连接, 磁盘内容读写, 和上游服务器通讯。</li> </ul> <p>大多数情况下 NGINX 推荐配置为一个核芯一个 worker 进程, 这样可以最高效利用硬件资源。可以在配置文件的 <a href="http://nginx.org/en/docs/ngx_core_module.html?#worker_processes">worker_processes</a> 指令中设置一个 <strong>auto</strong> 参数来启用。</p> <div class="highlighter-rouge"><pre class="highlight"><code>worker_processes auto; </code></pre> </div> <p>NGINX 启动后, 只有 worker 进程是忙碌的。 每个 worker 进程以非阻塞的方式处理多个连接, 减少上下文切换的次数。</p> <p>每个 worker 进程是单线程且独立运行的, 拿到新的连接并进行处理。进程间可以使用共享内存来共用缓存数据,会话持久数据还有其他共享资源。</p> <h3 id="深入-nginx-worker-进程">深入 NGINX Worker 进程</h3> <p><img src="https://cdn.wp.nginx.com/wp-content/uploads/2015/06/Screen-Shot-2015-06-08-at-12.39.48-PM.png" alt="HTTP State Machine in NGINX" /></p> <p>NGINX worker 进程使用 NGINX 配置进行初始化, 并获取一组由 master 进程提供的监听套接字。</p> <p>NGINX worker 进程首先等待监听套接字上的事件(<a href="http://nginx.org/en/docs/ngx_core_module.html?#accept_mutex">accept_mutex</a>和<a href="http://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/">kernel socket sharding</a>)。新接入的连接触发事件。连接分配到一个状态机,最常用的是 HTTP 状态机,但 NGINX 也为原生TCP流和一系列邮件协议(SMTP, IMAP 和 POP3)实现了状态机。</p> <p><img src="https://cdn.wp.nginx.com/wp-content/uploads/2015/06/Screen-Shot-2015-06-08-at-12.40.32-PM.png" alt="NGINX upstream services" title="Internet Requests" /></p> <p>状态机本质上是告诉 NGINX 如何处理请求的指令集。大部分 web 服务器也是像 NGINX 一样使用状态机来提供这样的功能,但内部实现不同。</p> <ul> <li>译注: 通过设置 accept_mutex 以串行化的方式唤醒一个 worker, 避免惊群。设置 reuseport(就是上面提到的 kernel socket sharding 特性) 后每个 worker 持有一个socket由内核决定哪个 worker 获得连接。</li> </ul> <h3 id="状态机的调度">状态机的调度</h3> <p>把状态机想像为国际象棋规则。每个 HTTP 事务都是一次国际象棋对局。棋盘的一边是 web 服务器,可以快速作出决定的象棋大师。另一边是远程客户端,通过相对慢速的网络访问网页或应用的浏览器。</p> <p>然而,游戏规则可能非常复杂。例如 web 服务器可能还需要和第三方通信(代理到上游)或和认证服务器交互。服务器上的第三方模块甚至可以拓展游戏规则。</p> <h4 id="阻塞的状态机">阻塞的状态机</h4> <p>回想前面对于进程和线程的描述,它们是操作系统可以在一个核芯上调度运行的一组指令集。大多数 web 服务器和 web 应用使用每个连接一个进程或每个连接一个线程的模型来下象棋。每个进程或线程都包含从头到尾进行下棋的指令。服务器运行过程中,耗费了大量的时间等待客户端采取进一步行动,即“阻塞”。</p> <p><img src="https://cdn.wp.nginx.com/wp-content/uploads/2015/06/Screen-Shot-2015-06-08-at-12.40.52-PM.png" alt="NGINX listen sockets" title="Blocking I/O" /></p> <ol> <li>web 服务器进程在监听新来的连接(客户端触发开局)。  2. 新的一局开始后,进程走了一步后就会阻塞以等待客户端响应。</li> <li>一旦对局结束, web 服务器进程还可能等待让客户端新开一局游戏(对应于 keepalive 连接)。如果连接关闭(客户端离开或发生超时), 进程又回到监听状态。</li> </ol> <p>重点要记住每个活跃的 HTTP 连接(每次国际象棋对局)都要求一个专门的进程或线程(一个棋手)。这种架构可以简单方便地进行第三方模块拓展(新规则)。但是存在极大的不平衡:由文件描述符和少量内存表示的相对轻量级的 HTTP 连接影射为一个单独的非常重量级的操作系统对象线程或进程。这方便了编程却极大地浪费了资源。</p> <h4 id="nginx-是真正的大师">NGINX 是真正的大师</h4> <p>你或许听说过车轮战吧?一个国际象棋大师同时和几十个对手下棋。</p> <p><img src="https://cdn.wp.nginx.com/wp-content/uploads/2015/06/Kiril-Georgiev.gif" alt="Kiril Georgiev, chess grandmaster" title="Kiril Georgiev" /></p> <p>这就是 NGINX worker 进程“下棋”的方式。每个 worker (记住,通常是每个核芯一个 worker) 都是一个可以同时进行上百场(事实上,成千上万)对局的棋手。</p> <p><img src="https://cdn.wp.nginx.com/wp-content/uploads/2015/06/Screen-Shot-2015-06-08-at-12.41.13-PM.png" alt="NGINX non-blocking event-driven architecture" title="Event-driven Architecture" /></p> <ol> <li>worker 等待监听和连接的套接字触发事件。</li> <li>worker 处理套接字上发生的事件: <ul> <li>监听套接字上的事件意味着客户端开始了新的对局。worker 创建一个新的连接套接字。</li> <li>连接套接字上的事件意味着客户端走了一步。worker 适当的应对。</li> </ul> </li> </ol> <p>Worker 从不在网络通讯上阻塞来等待它的“对手”(客户端)响应。当它走了一步后,worker 立即处理其他需要处理的对局或迎接新来的对手。</p> <h4 id="比阻塞式多进程的架构更快的原因">比阻塞式多进程的架构更快的原因</h4> <p>NGINX 可以很好地扩展,以支持每个工作进程成千上万的连接。每个新连接在 worker 进程中创建另一个文件描述符和消耗少量的内存。每个连接几乎没有额外的开销。NGINX进程可以保持在 CPU 中运行。上下文切换相对不频繁仅在进程没有更多工作需要完成时进行。</p> <p>在阻塞式每进程一个连接的方式中,每个连接需要大量的额外资源和开销还有上下文切换(进程在CPU中交换)非常频繁。</p> <p>进一步的解释可以查看这篇关于 NGINX 架构的<a href="http://www.aosabook.org/en/nginx.html">文章</a>, 由 NGINX 的企业发展副总裁和联合创始人 Andrew Alexeev 所写。</p> <p>加上适当的<a href="http://www.nginx.com/blog/tuning-nginx/">系统调优</a>, NGINX 可以扩展至每个 worker 进程处理数十万个并发HTTP连接并可以吸收流量尖峰(新对局的涌入),而毫不乱套。</p> <h3 id="nginx-配置更新和升级">NGINX 配置更新和升级</h3> <p>NGINX 这种使用少量 worker 进程的架构可以极其有效的更新配置文件乃至 NGINX 二进制执行文件本身.</p> <p><img src="https://cdn.wp.nginx.com/wp-content/uploads/2015/06/Screen-Shot-2015-06-08-at-12.41.33-PM.png" alt="NGINX load new configuration" title="Updating Configuration" /></p> <p>更新 NGINX 配置是容易、轻量和可靠的操作。一般是通过运行 <code class="highlighter-rouge">nginx -s reload</code> 命令,它会检查磁盘中的配置并给 master 进程发送 SIGHUP 信号。</p> <p>当 master 进程接收 SIGHUP, 它做了两件事情:</p> <ol> <li>加载配置并 fork 一组新的 worker 进程。这些 worker 进程马上开始接收新连接和处理网络请求(使用新的配置)。</li> <li>发送信号让旧的 worker 优雅退出。这些 worker 进程停止接收新连接。只要每个现有的 HTTP 请求完成, worker 进程干净地关闭连接(换句话说,就是没有活动的连接)。当旧 worker 进程所有连接完全关闭后便会退出。</li> </ol> <p>重新加载配置的过程会造成 CPU 和内存使用率少量上升, 但和活跃连接加载资源相比这一般不易察觉。你可以每秒多次更新配置(很多 NGINX 用户确实这么做)。少数情况下,当很多代 NGINX worker 进程等待连接关闭时会出现问题,但即使是这样也会很快解决。</p> <p>NGINX的二进制升级过程实现了高可用性,你可以随时升级,而不导致任何连接丢失,停机时间或服务中断。</p> <p><img src="https://cdn.wp.nginx.com/wp-content/uploads/2015/06/Screen-Shot-2015-06-08-at-12.41.51-PM.png" alt="NGINX load binary with no downtime" title="New Binary" /></p> <p>二进制升级过程和优雅地重新加载配置的方式类似。新的 master 进程和原来的 master 进程并行运行,共享监听的 socket。两个进程都是活动的,它们有相应的 worker 进程处理通讯。你可以给旧的 master 和它的 worker 进程发信号让它优雅退出。</p> <p>更多关于整个过程的描述在<a href="http://nginx.org/en/docs/control.html">控制 NGINX</a>。</p> <h3 id="结论">结论</h3> <p><a href="http://www.nginx.com/resources/library/infographic-inside-nginx/">Inside NGINX</a> 信息图提供对 NGINX 如何运作的高级概述,不过这简单的说明背后是逾十年的革新和优化。这使得 NGINX 在各种硬件上提供最佳性能的同时保持现代 web 应用程序所需的安全性和可靠性。</p> <p>如果你希望了解更多关于 NGINX 优化,请查看这些资源:</p> <ul> <li><a href="http://www.nginx.com/resources/webinars/installing-tuning-nginx/">Installing and Tuning NGINX for Performance</a> (webinar; <a href="https://speakerdeck.com/nginx/nginx-installation-and-tuning">slides</a> at Speaker Deck)</li> <li><a href="http://www.nginx.com/blog/tuning-nginx/">Tuning NGINX for Performance</a></li> <li><a href="http://www.aosabook.org/en/nginx.html">The Architecture of Open Source Applications – NGINX</a></li> <li><a href="http://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/">Socket Sharding in NGINX Release 1.9.1</a> (using the SO_REUSEPORT socket option)</li> </ul> Tue, 16 May 2017 21:16:49 +0000 https://alwayswithme.github.io/nginx/2017/05/16/inside-nginx.html https://alwayswithme.github.io/nginx/2017/05/16/inside-nginx.html nginx 改造匿名内部类 <p>最近接手的项目中,看到一个分页对象用了一种奇怪的实现方式。一个 <code class="highlighter-rouge">Paging</code> 接口定义了获取分页大小和页码的方法,后续的查询竟然每次都通过匿名内部类去实现这个接口,然后作为查询参数。</p> <h2 id="示例代码">示例代码</h2> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Paging</span> <span class="o">{</span> <span class="kt">int</span> <span class="nf">getSize</span><span class="o">();</span> <span class="kt">int</span> <span class="nf">getCurrentPage</span><span class="o">();</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">Test</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="n">Paging</span> <span class="n">paging</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Paging</span><span class="o">()</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getSize</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="mi">10</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getCurrentPage</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">args</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="o">}</span> <span class="o">};</span> <span class="c1">// 使用 paging 计算limit offset做分页查询</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <p>对这段代码不爽的地方</p> <ul> <li>冗长,可读性和维护性差</li> <li>编译出来会多几个形如 <code class="highlighter-rouge">xxx$1.class</code> 文件</li> </ul> <p>每个匿名内部类都要加载、链接、初始化虽然对现代JVM影响不大,但加载类过多还是有可能导致JDK7及以前版本的 <code class="highlighter-rouge">PernGem OOM</code> 和JDK8 <code class="highlighter-rouge">Metaspace OOM</code>。</p> <h2 id="改造方式">改造方式</h2> <p>本质就是一个实现了 <code class="highlighter-rouge">Paging</code> 接口 data class 就能解决,IDEA 也提供相应的重构方法。先转为内部类,在抽到其他地方统一修改。</p> <ul> <li>右键匿名类 -&gt; Refactor -&gt; Convert Anonymous to Inner <img src="/images/Screenshot_2017-04-08_14-26-39.jpg" alt="匿名类重构" /></li> <li>右键内部类 -&gt; Refactor -&gt; Move <img src="/images/Screenshot_2017-04-08_14-27-17.jpg" alt="内部类重构" /></li> </ul> <p>最终类文件变成</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyPaging</span> <span class="kd">implements</span> <span class="n">Paging</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">final</span> <span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">MyPaging</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">args</span> <span class="o">=</span> <span class="n">args</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getSize</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="mi">10</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getCurrentPage</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">args</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <p>感觉还是比较呆,再稍加修改,主要参考 MyBatis 的 RowBounds。除此之外,Spring Data 的 Pageable 及其实现类也是很优雅的分页实现。</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyPaging</span> <span class="kd">implements</span> <span class="n">Paging</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">DEFAULT_SIZE</span> <span class="o">=</span> <span class="mi">10</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">DEFAULT_PAGE</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">MyPaging</span> <span class="n">DEFAULT</span> <span class="o">=</span> <span class="k">new</span> <span class="n">MyPaging</span><span class="o">(</span><span class="n">DEFAULT_SIZE</span><span class="o">,</span> <span class="n">DEFAULT_PAGE</span><span class="o">);</span> <span class="kd">private</span> <span class="kt">int</span> <span class="n">size</span><span class="o">;</span> <span class="kd">private</span> <span class="kt">int</span> <span class="n">currentPage</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">static</span> <span class="n">MyPaging</span> <span class="nf">defaultPaging</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">DEFAULT</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="nf">MyPaging</span><span class="o">(</span><span class="kt">int</span> <span class="n">s</span><span class="o">,</span> <span class="kt">int</span> <span class="n">p</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">size</span> <span class="o">=</span> <span class="n">s</span><span class="o">;</span> <span class="k">this</span><span class="o">.</span><span class="na">currentPage</span> <span class="o">=</span> <span class="n">p</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getSize</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">size</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getCurrentPage</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">currentPage</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <p>现在通过两个参数创建<code class="highlighter-rouge">MyPaging</code>对象就可以了</p> <h2 id="其他思考">其他思考</h2> <p>匿名内部类主要是方便,但不利于复用,而且导致很多class文件。像Runable,Comparator这样只有一个抽象方法要实现的接口(由此也多了一个<code class="highlighter-rouge">@FunctionalInterface</code>的注解), Java 8 的lambda表达式感觉更优雅。</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Anonymous</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="n">Comparator</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">cmp1</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Comparator</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">compare</span><span class="o">(</span><span class="n">String</span> <span class="n">o1</span><span class="o">,</span> <span class="n">String</span> <span class="n">o2</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="mi">1</span><span class="o">;</span> <span class="o">}</span> <span class="o">};</span> <span class="n">Comparator</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">cmp2</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Comparator</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">compare</span><span class="o">(</span><span class="n">String</span> <span class="n">o1</span><span class="o">,</span> <span class="n">String</span> <span class="n">o2</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="mi">1</span><span class="o">;</span> <span class="o">}</span> <span class="o">};</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <p>像上面这段代码最终会生成三个类文件,哪怕两个匿名内部类完全一致。 <img src="/images/Screenshot_2017-04-08_15-46-41.jpg" alt="匿名类生成三个class" /> 这时可以改为Lambda表达式,使代码更简洁优雅。 <img src="/images/Screenshot_2017-04-08_15-45-54.jpg" alt="lambda还是一个class" /></p> <p>两张图对比,还可以得出的结论</p> <ul> <li>Lambda 表达式不是匿名类的语法糖,有自己的实现方式</li> <li>匿名类最终编译的文件比 Lambda 大,674 B + 812 B + 812 B &gt; 1.4 KB</li> </ul> <h2 id="最后补充">最后补充</h2> <p>回到主题,项目里各处用到这个 Paging 都是这种匿名内部类的写法,也不知道生产环境存在多少class文件。按照我上面的对比代码,把 Comparator 增加至10个,每个<code class="highlighter-rouge">xxx$1..10.class</code>都占812 B,难怪搭建环境时 Tomcat 的老是出现 <code class="highlighter-rouge">PernGem OOM</code>,但也和大量 JSP 和 Servlet 有关。</p> <p>最近看了很多JD,很有感觉的一段话是:</p> <blockquote> <p>我们希望你对互联网或J2EE应用开发的最新潮流有关注,喜欢去看及尝试最新的技术,追求编写优雅的代码,从技术趋势和思路上能影响技术团队</p> </blockquote> <p>也终于理解为什么《The Pragmatic Programmer》和《Refactoring: Improving the Design of Existing Code》会多次强调 <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY 原则</a>的重要性</p> Sat, 08 Apr 2017 13:32:54 +0000 https://alwayswithme.github.io/java/2017/04/08/convert-anonymous-class.html https://alwayswithme.github.io/java/2017/04/08/convert-anonymous-class.html java 使用Optional <p>Java 8 引入了一个新的类<code class="highlighter-rouge">java.util.Optinal</code>来处理<code class="highlighter-rouge">null</code>引用,熟悉 Guava 的人应该对这个不陌生, Guava 的<a href="https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained">文档</a>开篇就是介绍这个东西。<br /> Guava 的 <code class="highlighter-rouge">Optional</code> 作用有限,一般用来作为方法的返回值如<code class="highlighter-rouge">Optional&lt;T&gt;</code>,表示可能缺失T,迫使调用者做对应处理。但 Java 的 <code class="highlighter-rouge">Optional</code> 可以配合方法引用和函数接口做更多的事情。类似还有 Guava 的 <code class="highlighter-rouge">Joiner</code> 和 Java 8 的 <code class="highlighter-rouge">StringJoiner</code>。</p> <p><code class="highlighter-rouge">Optional</code> 结构比较简单,可以看作成员变量<code class="highlighter-rouge">value</code>的容器。</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">Optional</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="o">{</span> <span class="cm">/** * Common instance for {@code empty()}. */</span> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">Optional</span><span class="o">&lt;?&gt;</span> <span class="n">EMPTY</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Optional</span><span class="o">&lt;&gt;();</span> <span class="cm">/** * If non-null, the value; if null, indicates no value is present */</span> <span class="kd">private</span> <span class="kd">final</span> <span class="n">T</span> <span class="n">value</span><span class="o">;</span> <span class="c1">// ...省略其他代码</span> <span class="o">}</span> </code></pre> </div> <h2 id="构造方法">构造方法</h2> <p><code class="highlighter-rouge">Optional</code> 的构造方法私有,通过三个静态工厂方法构造。</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">Optional</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">empty</span> <span class="o">=</span> <span class="n">Optional</span><span class="o">.</span><span class="na">empty</span><span class="o">();</span> <span class="c1">// 没有值的Optinal,返回静态变量EMPTY</span> <span class="n">Optional</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">present</span> <span class="o">=</span> <span class="n">Optional</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"str"</span><span class="o">);</span> <span class="c1">// 包含非空值的Optional</span> <span class="n">String</span> <span class="n">maybeNullStr</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="n">Optional</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">absent</span> <span class="o">=</span> <span class="n">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="n">maybeNullStr</span><span class="o">);</span> <span class="c1">// 内部会判断,如果为空返回empty,不为空返回包含值的Optinal</span> </code></pre> </div> <h2 id="orelse-orelseget">orElse, orElseGet</h2> <p>如果值不存在,返回默认值。参数需要提供一个默认值,其中orElse直接提供,orElseGet则通过函数接口提供。</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">String</span> <span class="n">arg</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">arg</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="s">"default value"</span> <span class="o">:</span> <span class="n">arg</span><span class="o">);</span> <span class="n">Optional</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">argOpt</span> <span class="o">=</span> <span class="n">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="n">arg</span><span class="o">);</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">argOpt</span><span class="o">.</span><span class="na">orElse</span><span class="o">(</span><span class="s">"default value"</span><span class="o">));</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">argOpt</span><span class="o">.</span><span class="na">orElseGet</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="s">"default value"</span><span class="o">));</span> </code></pre> </div> <h2 id="orelsethrow">orElseThrow</h2> <p>针对值不存在,抛出异常的情况,可以通过orElseThrow方便地做到。</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">arg</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">();</span> <span class="o">}</span> <span class="c1">// 方法引用不能传参</span> <span class="n">argOpt</span><span class="o">.</span><span class="na">orElseThrow</span><span class="o">(</span><span class="nl">IllegalArgumentException:</span><span class="o">:</span><span class="k">new</span><span class="o">);</span> <span class="c1">// 如果需要message,可以用 lambda 表达式</span> <span class="n">argOpt</span><span class="o">.</span><span class="na">orElseThrow</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="k">new</span> <span class="n">IllegalArgumentException</span><span class="o">(</span><span class="s">"must not null"</span><span class="o">));</span> </code></pre> </div> <h2 id="ispresent-ifpresent">isPresent, ifPresent</h2> <p>前者判断Optinal的值是否存在,然后通过get取出。后者提供一个函数,值存在则将其消费。</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">Optional</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">value</span> <span class="o">=</span> <span class="n">Optional</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"value"</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">value</span><span class="o">.</span><span class="na">isPresent</span><span class="o">())</span> <span class="o">{</span> <span class="n">value</span><span class="o">.</span><span class="na">get</span><span class="o">();</span> <span class="o">}</span> <span class="n">value</span><span class="o">.</span><span class="na">ifPresent</span><span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">);</span> </code></pre> </div> <h2 id="map-flatmap">map, flatMap</h2> <p>map要提供一个转换函数,转换后再通过<code class="highlighter-rouge">Optional.ofNullable()</code>返回</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span><span class="o">&lt;</span><span class="n">U</span><span class="o">&gt;</span> <span class="n">Optional</span><span class="o">&lt;</span><span class="n">U</span><span class="o">&gt;</span> <span class="nf">map</span><span class="o">(</span><span class="n">Function</span><span class="o">&lt;?</span> <span class="kd">super</span> <span class="n">T</span><span class="o">,</span> <span class="o">?</span> <span class="kd">extends</span> <span class="n">U</span><span class="o">&gt;</span> <span class="n">mapper</span><span class="o">)</span> <span class="o">{</span> <span class="n">Objects</span><span class="o">.</span><span class="na">requireNonNull</span><span class="o">(</span><span class="n">mapper</span><span class="o">);</span> <span class="k">if</span> <span class="o">(!</span><span class="n">isPresent</span><span class="o">())</span> <span class="k">return</span> <span class="nf">empty</span><span class="o">();</span> <span class="k">else</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="n">mapper</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">value</span><span class="o">));</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <p>flatMap与map类似,但转换函数的返回值必须为<code class="highlighter-rouge">Optional</code>,转换后不会再包装<code class="highlighter-rouge">Optional</code>以避免生成多重<code class="highlighter-rouge">Optional</code>。</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span><span class="o">&lt;</span><span class="n">U</span><span class="o">&gt;</span> <span class="n">Optional</span><span class="o">&lt;</span><span class="n">U</span><span class="o">&gt;</span> <span class="nf">flatMap</span><span class="o">(</span><span class="n">Function</span><span class="o">&lt;?</span> <span class="kd">super</span> <span class="n">T</span><span class="o">,</span> <span class="n">Optional</span><span class="o">&lt;</span><span class="n">U</span><span class="o">&gt;&gt;</span> <span class="n">mapper</span><span class="o">)</span> <span class="o">{</span> <span class="n">Objects</span><span class="o">.</span><span class="na">requireNonNull</span><span class="o">(</span><span class="n">mapper</span><span class="o">);</span> <span class="k">if</span> <span class="o">(!</span><span class="n">isPresent</span><span class="o">())</span> <span class="k">return</span> <span class="nf">empty</span><span class="o">();</span> <span class="k">else</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Objects</span><span class="o">.</span><span class="na">requireNonNull</span><span class="o">(</span><span class="n">mapper</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">value</span><span class="o">));</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <h2 id="注意事项">注意事项</h2> <p>声明为Optional的字段或返回值不可为null,缺失值要用<code class="highlighter-rouge">Optional.empty()</code>代替,缺失值要用<code class="highlighter-rouge">Optional.empty()</code>代替,缺失值要用<code class="highlighter-rouge">Optional.empty()</code>代替,不然还是会导致<code class="highlighter-rouge">NullPointerException</code>。 只要Optional不为null,它的API就可以安全的调用。类似于<a href="https://en.wikipedia.org/wiki/Null_Object_pattern">Null Object</a>的设计模式。</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">Optional</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">optional</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="n">optional</span><span class="o">.</span><span class="na">isPresent</span><span class="o">();</span> <span class="c1">// NPE</span> </code></pre> </div> <h2 id="see-also">See Also</h2> <ul> <li><a href="https://dzone.com/articles/java-8-optional-avoid-null-and">Java 8 Optional - Avoid Null and NullPointerException Altogether - and Keep It Pretty</a></li> <li><a href="http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html">Tired of Null Pointer Exceptions? Consider Using Java SE 8’s Optional!</a></li> </ul> Fri, 10 Mar 2017 21:26:42 +0000 https://alwayswithme.github.io/jekyll/update/2017/03/10/use-optional.html https://alwayswithme.github.io/jekyll/update/2017/03/10/use-optional.html jekyll update 多线程模拟抓牌 <h2 id="鹅厂笔试题">鹅厂笔试题</h2> <p>请模拟斗地主抓牌,扑克牌的值为1~54,随机选取地主,并从地主开始按顺序抓牌,直到牌剩余三张。要求,用线程模拟玩家抓牌。</p> <h2 id="思路">思路</h2> <p>用三个线程表示玩家,三个线程编号。对剩余的牌数取模计算顺序,编号和顺序相同时,开始抓牌。否则等待。线程重复执行,直到每个线程拿到的牌数等于17。</p> <h2 id="完整代码">完整代码</h2> <p>省略了随机选地主的过程,用 <code class="highlighter-rouge">Lock</code> 和 <code class="highlighter-rouge">Condition</code> 来进行线程同步。放到线程池执行,主线程调用 <code class="highlighter-rouge">shutdown()</code> 等待线程池的任务执行完。</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CardPlayer</span> <span class="kd">implements</span> <span class="n">Runnable</span> <span class="o">{</span> <span class="kd">private</span> <span class="n">ReentrantLock</span> <span class="n">lock</span><span class="o">;</span> <span class="kd">private</span> <span class="n">Condition</span> <span class="n">takeCard</span><span class="o">;</span> <span class="kd">private</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Integer</span><span class="o">&gt;</span> <span class="n">holdCard</span><span class="o">;</span> <span class="kd">private</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Integer</span><span class="o">&gt;</span> <span class="n">cardStack</span><span class="o">;</span> <span class="kd">private</span> <span class="kt">int</span> <span class="n">order</span><span class="o">;</span> <span class="kd">private</span> <span class="n">String</span> <span class="n">name</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">CardPlayer</span><span class="o">(</span><span class="n">ReentrantLock</span> <span class="n">lock</span><span class="o">,</span> <span class="n">Condition</span> <span class="n">takeCard</span><span class="o">,</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Integer</span><span class="o">&gt;</span> <span class="n">cardStack</span><span class="o">,</span> <span class="kt">int</span> <span class="n">order</span><span class="o">,</span> <span class="n">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">lock</span> <span class="o">=</span> <span class="n">lock</span><span class="o">;</span> <span class="k">this</span><span class="o">.</span><span class="na">takeCard</span> <span class="o">=</span> <span class="n">takeCard</span><span class="o">;</span> <span class="k">this</span><span class="o">.</span><span class="na">cardStack</span> <span class="o">=</span> <span class="n">cardStack</span><span class="o">;</span> <span class="k">this</span><span class="o">.</span><span class="na">order</span> <span class="o">=</span> <span class="n">order</span><span class="o">;</span> <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span> <span class="k">this</span><span class="o">.</span><span class="na">holdCard</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">&lt;&gt;(</span><span class="mi">17</span><span class="o">);</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span> <span class="n">lock</span><span class="o">.</span><span class="na">lock</span><span class="o">();</span> <span class="k">try</span> <span class="o">{</span> <span class="k">while</span> <span class="o">(</span><span class="n">holdCard</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">&lt;</span> <span class="mi">17</span><span class="o">)</span> <span class="o">{</span> <span class="k">while</span> <span class="o">((</span><span class="mi">54</span> <span class="o">-</span> <span class="n">cardStack</span><span class="o">.</span><span class="na">size</span><span class="o">())</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">!=</span> <span class="n">order</span><span class="o">)</span> <span class="o">{</span> <span class="n">takeCard</span><span class="o">.</span><span class="na">await</span><span class="o">();</span> <span class="o">}</span> <span class="n">Integer</span> <span class="n">card</span> <span class="o">=</span> <span class="n">cardStack</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> <span class="n">holdCard</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">card</span><span class="o">);</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%-4s 拿到牌 %02d"</span><span class="o">,</span> <span class="n">name</span><span class="o">,</span> <span class="n">card</span><span class="o">));</span> <span class="n">takeCard</span><span class="o">.</span><span class="na">signalAll</span><span class="o">();</span> <span class="o">}</span> <span class="n">takeCard</span><span class="o">.</span><span class="na">signalAll</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">lock</span><span class="o">.</span><span class="na">unlock</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">InterruptedException</span> <span class="o">{</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Integer</span><span class="o">&gt;</span> <span class="n">cardStack</span> <span class="o">=</span> <span class="n">IntStream</span><span class="o">.</span><span class="na">rangeClosed</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">54</span><span class="o">).</span><span class="na">boxed</span><span class="o">().</span><span class="na">collect</span><span class="o">(</span><span class="n">Collectors</span><span class="o">.</span><span class="na">toList</span><span class="o">());</span> <span class="c1">// Collections.shuffle(list);</span> <span class="n">ReentrantLock</span> <span class="n">lock</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ReentrantLock</span><span class="o">();</span> <span class="n">Condition</span> <span class="n">takeCard</span> <span class="o">=</span> <span class="n">lock</span><span class="o">.</span><span class="na">newCondition</span><span class="o">();</span> <span class="n">CardPlayer</span> <span class="n">p1</span> <span class="o">=</span> <span class="k">new</span> <span class="n">CardPlayer</span><span class="o">(</span><span class="n">lock</span><span class="o">,</span> <span class="n">takeCard</span><span class="o">,</span> <span class="n">cardStack</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="s">"地主"</span><span class="o">);</span> <span class="n">CardPlayer</span> <span class="n">p2</span> <span class="o">=</span> <span class="k">new</span> <span class="n">CardPlayer</span><span class="o">(</span><span class="n">lock</span><span class="o">,</span> <span class="n">takeCard</span><span class="o">,</span> <span class="n">cardStack</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="s">"农民1"</span><span class="o">);</span> <span class="n">CardPlayer</span> <span class="n">p3</span> <span class="o">=</span> <span class="k">new</span> <span class="n">CardPlayer</span><span class="o">(</span><span class="n">lock</span><span class="o">,</span> <span class="n">takeCard</span><span class="o">,</span> <span class="n">cardStack</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="s">"农民2"</span><span class="o">);</span> <span class="n">ExecutorService</span> <span class="n">executorService</span> <span class="o">=</span> <span class="n">Executors</span><span class="o">.</span><span class="na">newFixedThreadPool</span><span class="o">(</span><span class="mi">3</span><span class="o">);</span> <span class="n">executorService</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">p1</span><span class="o">);</span> <span class="n">executorService</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">p2</span><span class="o">);</span> <span class="n">executorService</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">p3</span><span class="o">);</span> <span class="n">executorService</span><span class="o">.</span><span class="na">shutdown</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> Sun, 05 Mar 2017 11:00:39 +0000 https://alwayswithme.github.io/jekyll/update/2017/03/05/multithread-play-card.html https://alwayswithme.github.io/jekyll/update/2017/03/05/multithread-play-card.html jekyll update BCrypt 加密 <p>采用MD5或SHA-2家族的不可逆哈希函数混淆密码,由于它们被设计用来快速的计算数据的摘要值,通常是不可靠的。随着计算机性能的提高和GPU并行运算优化等手段可以越来越快的计算,降低了暴力破解的难度。<br /> BCrypt是一种专门的密码散列函数,在1999年首次发布,各种语言都已有相应实现。它通过引入一个工作因子(work factor)以对抗<a href="https://zh.wikipedia.org/zh-hans/%E6%91%A9%E5%B0%94%E5%AE%9A%E5%BE%8B">摩尔定律</a>,随着计算能力的提高,也可以相应的加大这个因子提高暴破成本。而且由于计算时间较慢和难以让GPU有效的计算,可以使安全级别大大提高。因此被称为 <strong>A Future-Adaptable Password Scheme</strong> 。</p> <h2 id="bcrypt-格式">BCrypt 格式</h2> <p>加密字符串一般类似于<code class="highlighter-rouge">$2a$10$7QDYxuYJBCEKu1q8IMHYg.3lq6OTiA5seEjtnYGOccsC0MkLtvJrS</code>,可以分为四个部分</p> <div class="highlighter-rouge"><pre class="highlight"><code>$&lt;id&gt;$&lt;cost&gt;$&lt;salt&gt;&lt;digest&gt; </code></pre> </div> <ul> <li><code class="highlighter-rouge">&lt;id&gt;</code> 表示哈希算法和格式, 一到两个字符</li> <li><code class="highlighter-rouge">&lt;cost&gt;</code> 04 ~ 31, 工作因子,要重复迭代<code class="highlighter-rouge">2**cost</code>次,两个字符</li> <li><code class="highlighter-rouge">&lt;salt&gt;</code> 128位表示的盐,使用特殊的Base64编码为22个字符</li> <li><code class="highlighter-rouge">&lt;digest&gt;</code> 最终的结果值,184位编码为31个字符</li> </ul> <blockquote> <p>3(美元符号) + 1 or 2 + 2 + 22 + 34 = 59 or 60</p> </blockquote> <p>所以总长比较固定,数据库可以采用<code class="highlighter-rouge">CHAR(60)</code>,且无需其他字段存储盐</p> <h2 id="使用示例">使用示例</h2> <p>示例中用到的<a href="docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/crypto/bcrypt/BCrypt.html">BCrypt</a>由<code class="highlighter-rouge">Spring Security</code>提供</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">PasswordHashTest</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">plainPasswd</span> <span class="o">=</span> <span class="s">"1234@abcd"</span><span class="o">;</span> <span class="nd">@Test</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">normalTest</span><span class="o">()</span> <span class="o">{</span> <span class="n">String</span> <span class="n">hash1</span> <span class="o">=</span> <span class="n">BCrypt</span><span class="o">.</span><span class="na">hashpw</span><span class="o">(</span><span class="n">plainPasswd</span><span class="o">,</span> <span class="n">BCrypt</span><span class="o">.</span><span class="na">gensalt</span><span class="o">());</span> <span class="n">Assert</span><span class="o">.</span><span class="na">assertTrue</span><span class="o">(</span><span class="n">BCrypt</span><span class="o">.</span><span class="na">checkpw</span><span class="o">(</span><span class="n">plainPasswd</span><span class="o">,</span> <span class="n">hash1</span><span class="o">));</span> <span class="o">}</span> <span class="nd">@Test</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">strongerTest</span><span class="o">()</span> <span class="o">{</span> <span class="c1">// 通常这个测试会跑比较久,计算能力过剩可改为BCrypt.gensalt(31)</span> <span class="n">String</span> <span class="n">hash2</span> <span class="o">=</span> <span class="n">BCrypt</span><span class="o">.</span><span class="na">hashpw</span><span class="o">(</span><span class="n">plainPasswd</span><span class="o">,</span> <span class="n">BCrypt</span><span class="o">.</span><span class="na">gensalt</span><span class="o">(</span><span class="mi">14</span><span class="o">));</span> <span class="n">Assert</span><span class="o">.</span><span class="na">assertTrue</span><span class="o">(</span><span class="n">BCrypt</span><span class="o">.</span><span class="na">checkpw</span><span class="o">(</span><span class="n">plainPasswd</span><span class="o">,</span> <span class="n">hash2</span><span class="o">));</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <h2 id="see-also">See Also</h2> <ul> <li><a href="https://en.wikipedia.org/wiki/Bcrypt">BCrypt Wikipedia</a></li> <li><a href="http://stackoverflow.com/questions/5881169/what-column-type-length-should-i-use-for-storing-a-bcrypt-hashed-password-in-a-d">BCrypt格式介绍</a></li> <li><a href="https://codahale.com/how-to-safely-store-a-password/">How To Safely Store A Password</a></li> <li><a href="http://security.stackexchange.com/questions/133239/what-is-the-specific-reason-to-prefer-bcrypt-or-pbkdf2-over-sha256-crypt-in-pass">为何使用BCrypt而不是SHA</a></li> </ul> Mon, 27 Feb 2017 09:38:18 +0000 https://alwayswithme.github.io/jekyll/update/2017/02/27/bcrypt.html https://alwayswithme.github.io/jekyll/update/2017/02/27/bcrypt.html jekyll update JDBC 的代码简化 <p>很多JDBC的教程和博文还在用较旧的方式来访问数据库。<br /> 利用 Java 6 的 <code class="highlighter-rouge">JDBC 4.0 API</code> 和 Java 7 的 <code class="highlighter-rouge">try-with-resources</code> 语法可以大大简化冗长的代码。</p> <h2 id="java-7-以前的写法">Java 7 以前的写法</h2> <p>可以看到有很多资源清理的操作,也要手动装载 JDBC 驱动</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">URL</span> <span class="o">=</span> <span class="s">"jdbc:mysql://localhost"</span><span class="o">;</span> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">USER</span> <span class="o">=</span> <span class="s">"root"</span><span class="o">;</span> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">PASSWD</span> <span class="o">=</span> <span class="s">"secret"</span><span class="o">;</span> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">STATEMENT</span> <span class="o">=</span> <span class="s">"SHOW DATABASES"</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">priorJava7JDBC</span><span class="o">()</span> <span class="o">{</span> <span class="n">Connection</span> <span class="n">conn</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="n">Statement</span> <span class="n">stmt</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="n">ResultSet</span> <span class="n">rs</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="n">Class</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="s">"com.mysql.jdbc.Driver"</span><span class="o">);</span> <span class="c1">// 注册驱动</span> <span class="n">conn</span> <span class="o">=</span> <span class="n">DriverManager</span><span class="o">.</span><span class="na">getConnection</span><span class="o">(</span><span class="n">URL</span><span class="o">,</span> <span class="n">USER</span><span class="o">,</span> <span class="n">PASSWD</span><span class="o">);</span> <span class="c1">// 打开连接</span> <span class="n">stmt</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="na">createStatement</span><span class="o">();</span> <span class="n">rs</span> <span class="o">=</span> <span class="n">stmt</span><span class="o">.</span><span class="na">executeQuery</span><span class="o">(</span><span class="n">STATEMENT</span><span class="o">);</span> <span class="c1">// 执行语句</span> <span class="c1">// 处理结果</span> <span class="k">while</span> <span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">next</span><span class="o">())</span> <span class="o">{</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"Database"</span><span class="o">));</span> <span class="o">}</span> <span class="c1">// 资源清理</span> <span class="n">rs</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="n">stmt</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="n">conn</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">closeQuietly</span><span class="o">(</span><span class="n">rs</span><span class="o">);</span> <span class="n">closeQuietly</span><span class="o">(</span><span class="n">stmt</span><span class="o">);</span> <span class="n">closeQuietly</span><span class="o">(</span><span class="n">conn</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">closeQuietly</span><span class="o">(</span><span class="n">AutoCloseable</span> <span class="n">closeable</span><span class="o">)</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="k">if</span><span class="o">(</span><span class="n">closeable</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">closeable</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <h2 id="java-7-之后的写法">Java 7 之后的写法</h2> <p>通过<code class="highlighter-rouge">try-with-resources</code> 省略了资源清理的代码,类似与 Python 的 <code class="highlighter-rouge">with</code> 语法,也无需注册驱动</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">afterJava7JDBC</span><span class="o">()</span> <span class="o">{</span> <span class="k">try</span> <span class="o">(</span><span class="n">Connection</span> <span class="n">connection</span> <span class="o">=</span> <span class="n">DriverManager</span><span class="o">.</span><span class="na">getConnection</span><span class="o">(</span><span class="n">URL</span><span class="o">,</span> <span class="n">USER</span><span class="o">,</span> <span class="n">PASSWD</span><span class="o">);</span> <span class="n">PreparedStatement</span> <span class="n">stmt</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="n">STATEMENT</span><span class="o">))</span> <span class="o">{</span> <span class="k">try</span> <span class="o">(</span><span class="n">ResultSet</span> <span class="n">rs</span> <span class="o">=</span> <span class="n">stmt</span><span class="o">.</span><span class="na">executeQuery</span><span class="o">())</span> <span class="o">{</span> <span class="k">while</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">next</span><span class="o">())</span> <span class="o">{</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"Database"</span><span class="o">));</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">SQLException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <h2 id="try-with-resources-异常处理方式">try-with-resources 异常处理方式</h2> <p>try-with-resources 可以理解为自动调用 close 方法的语法糖,但异常处理有差异,看下面的例子</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">static</span> <span class="n">String</span> <span class="nf">readFirstLineFromFileWithFinallyBlock</span><span class="o">(</span><span class="n">String</span> <span class="n">path</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="o">{</span> <span class="n">BufferedReader</span> <span class="n">br</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="n">FileReader</span><span class="o">(</span><span class="n">path</span><span class="o">));</span> <span class="k">try</span> <span class="o">{</span> <span class="k">return</span> <span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">();</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">br</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="n">br</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">static</span> <span class="n">String</span> <span class="nf">readFirstLineFromFile</span><span class="o">(</span><span class="n">String</span> <span class="n">path</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="o">{</span> <span class="k">try</span> <span class="o">(</span><span class="n">BufferedReader</span> <span class="n">br</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="n">FileReader</span><span class="o">(</span><span class="n">path</span><span class="o">)))</span> <span class="o">{</span> <span class="k">return</span> <span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <p><em>readFirstLineFromFileWithFinallyBlock</em>方法如果 readLine 和 close 同时抛出异常,方法的异常会是 <em>finally</em> 语句块的异常。<em>try</em> 语句块的异常被suppressed。 相比之下,<em>readFirstLineFromFile</em>方法如果 readLine 和 try-with-resources 调 close 方法都抛出异常,方法的异常会是 <em>try</em> 语句块的异常,即 readLine 的异常, try-with-resources 的异常被suppressed。可以通过抛出的异常调用 Throwable.getSuppressed 方法获得 try-with-resources 的异常。</p> <p>原文描述如下:</p> <blockquote> <p>However, in this example, if the methods readLine and close both throw exceptions, then the method readFirstLineFromFileWithFinallyBlock throws the exception thrown from the finally block; the exception thrown from the try block is suppressed. In contrast, in the example readFirstLineFromFile, if exceptions are thrown from both the try block and the try-with-resources statement, then the method readFirstLineFromFile throws the exception thrown from the try block; the exception thrown from the try-with-resources block is suppressed. In Java SE 7 and later, you can retrieve suppressed exceptions; see the section Suppressed Exceptions for more information.</p> </blockquote> <h2 id="还可以更简便吗">还可以更简便吗</h2> <p>上面的代码虽然有一定程度简化,但还是需要打开连接、执行语句、遍历结果集等操作, 通过 Spring 的 <code class="highlighter-rouge">JdbcTemplate</code> 配合 Java 8 的流式api以及lambda表达式可以让代码变得更加简洁,接近One-Liners。例如:</p> <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">jdbcTemplate</span><span class="o">.</span><span class="na">query</span><span class="o">(</span> <span class="s">"SELECT id, first_name, last_name FROM customers WHERE first_name = ?"</span><span class="o">,</span> <span class="k">new</span> <span class="n">Object</span><span class="o">[]</span> <span class="o">{</span> <span class="s">"Josh"</span> <span class="o">},</span> <span class="o">(</span><span class="n">rs</span><span class="o">,</span> <span class="n">rowNum</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="k">new</span> <span class="n">Customer</span><span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">getLong</span><span class="o">(</span><span class="s">"id"</span><span class="o">),</span> <span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"first_name"</span><span class="o">),</span> <span class="n">rs</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"last_name"</span><span class="o">))</span> <span class="o">).</span><span class="na">forEach</span><span class="o">(</span><span class="n">customer</span> <span class="o">-&gt;</span> <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="n">customer</span><span class="o">.</span><span class="na">toString</span><span class="o">()));</span> </code></pre> </div> <p>具体可看<a href="https://spring.io/guides/gs/relational-data-access/">教程</a></p> <h2 id="see-also">See Also</h2> <ul> <li><a href="http://archive.oreilly.com/pub/a/onjava/2006/08/02/jjdbc-4-enhancements-in-java-se-6.html">JDBC 4.0 Enhancements in Java SE 6</a></li> <li><a href="http://docs.oracle.com/javadb/10.8.3.0/ref/rrefjdbc4_0summary.html">JDBC 4.0 and 4.1 features</a></li> <li><a href="https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html">The try-with-resources Statement</a></li> </ul> Tue, 21 Feb 2017 11:01:53 +0000 https://alwayswithme.github.io/jekyll/update/2017/02/21/simplify-jdbc.html https://alwayswithme.github.io/jekyll/update/2017/02/21/simplify-jdbc.html jekyll update Shadowsocks 翻墙 <p>记录在 VPS 搭建 shadowsocks 翻墙全过程</p> <h2 id="准备-vps">准备 VPS</h2> <p>选择 <a href="https://m.do.co/c/61165f60138e">DigitalOcean</a> 作为 VPS:</p> <ol> <li>稳定性不错</li> <li>有途径获得35刀免费试用7个月</li> <li>kvm 虚拟化,支持调节内核参数</li> </ol> <h3 id="如何获得35美元">如何获得35美元?</h3> <ul> <li>通过<a href="https://m.do.co/c/61165f60138e">推广链接</a>注册</li> <li>绑定信用卡或用paypal激活账户,获得10刀</li> <li>注册<a href="https://codeanywhere.com/signup">Codeanywhere</a>帐号</li> <li>注册好后依次点击<code class="highlighter-rouge">File</code> -&gt; <code class="highlighter-rouge">New Connection</code> -&gt; <code class="highlighter-rouge">Digital Ocean</code>, 点击<code class="highlighter-rouge">Get Coupon</code></li> <li>获得优惠后在DigitalOcean的Billing界面填入, 再获得25刀</li> </ul> <h2 id="安装-shadowsocks-libev">安装 <a href="https://github.com/shadowsocks/shadowsocks-libev#installation">shadowsocks-libev</a></h2> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>~ <span class="c"># dnf copr enable librehat/shadowsocks</span> ~ <span class="c"># dnf update</span> ~ <span class="c"># dnf install shadowsocks-libev</span> </code></pre> </div> <p>编辑服务器端配置文件 <code class="highlighter-rouge">/etc/shadowsocks-libev/config.json</code></p> <div class="language-json highlighter-rouge"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nt">"server"</span><span class="p">:</span><span class="s2">"0.0.0.0"</span><span class="p">,</span><span class="w"> </span><span class="nt">"server_port"</span><span class="p">:</span><span class="mi">10718</span><span class="p">,</span><span class="w"> </span><span class="nt">"local_port"</span><span class="p">:</span><span class="mi">1080</span><span class="p">,</span><span class="w"> </span><span class="nt">"password"</span><span class="p">:</span><span class="s2">"passwd"</span><span class="p">,</span><span class="w"> </span><span class="nt">"timeout"</span><span class="p">:</span><span class="mi">800</span><span class="p">,</span><span class="w"> </span><span class="nt">"method"</span><span class="p">:</span><span class="s2">"chacha20"</span><span class="p">,</span><span class="w"> </span><span class="nt">"fast_open"</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">需要内核</span><span class="w"> </span><span class="err">3.7+</span><span class="w"> </span><span class="nt">"mode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tcp_and_udp"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>systemctl <span class="nb">enable </span>shadowsocks-libev.service <span class="c"># 开机自启动</span> systemctl start shadowsocks-libev.service <span class="c"># 运行ss-server</span> </code></pre> </div> <h2 id="修改内核参数">修改内核参数</h2> <p>编辑<code class="highlighter-rouge">/etc/sysctl.conf</code></p> <div class="highlighter-rouge"><pre class="highlight"><code># max open files fs.file-max = 51200 # max read buffer net.core.rmem_max = 67108864 # max write buffer net.core.wmem_max = 67108864 # default read buffer net.core.rmem_default = 65536 # default write buffer net.core.wmem_default = 65536 # max processor input queue net.core.netdev_max_backlog = 4096 # max backlog net.core.somaxconn = 4096 # resist SYN flood attacks net.ipv4.tcp_syncookies = 1 # reuse timewait sockets when safe net.ipv4.tcp_tw_reuse = 1 # turn off fast timewait sockets recycling net.ipv4.tcp_tw_recycle = 0 # short FIN timeout net.ipv4.tcp_fin_timeout = 30 # short keepalive time net.ipv4.tcp_keepalive_time = 1200 # outbound port range net.ipv4.ip_local_port_range = 10000 65000 # max SYN backlog net.ipv4.tcp_max_syn_backlog = 4096 # max timewait sockets held by system simultaneously net.ipv4.tcp_max_tw_buckets = 5000 # turn on TCP Fast Open on both client and server side net.ipv4.tcp_fastopen = 3 # TCP receive buffer net.ipv4.tcp_rmem = 4096 87380 67108864 # TCP write buffer net.ipv4.tcp_wmem = 4096 65536 67108864 # turn on path MTU discovery net.ipv4.tcp_mtu_probing = 1 # require linux kernel 4.9 net.core.default_qdisc=fq # for high-latency network net.ipv4.tcp_congestion_control =bbr # for strongswan vpn net.ipv4.ip_forward = 1 </code></pre> </div> <p>然后执行</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>sysctl -p </code></pre> </div> Sun, 19 Feb 2017 09:54:53 +0000 https://alwayswithme.github.io/jekyll/update/2017/02/19/setup-kcptun-shadowsocks.html https://alwayswithme.github.io/jekyll/update/2017/02/19/setup-kcptun-shadowsocks.html jekyll update MongoDB 笔记 <p>MongoDB 使用备忘</p> <h3 id="备份mongodump">备份<a href="https://docs.mongodb.com/manual/reference/program/mongodump/"><code class="highlighter-rouge">mongodump</code></a></h3> <figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># 备份整个数据库并压缩为 gzip 包到指定路径</span> mongodump --archive<span class="o">=</span>/tmp/mongodb.gz --db <span class="nb">test</span> --gzip --host &lt;host&gt;:&lt;port&gt; <span class="c"># 备份整个数据库并压缩为 gzip 包到标准输入</span> mongodump --archive --db <span class="nb">test</span> --gzip --host &lt;host&gt;:&lt;port&gt;</code></pre></figure> <h3 id="恢复mongorestore">恢复<a href="https://docs.mongodb.com/manual/reference/program/mongorestore/"><code class="highlighter-rouge">mongorestore</code></a></h3> <figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># 从 gzip 压缩包中恢复,加 `--drop` 恢复前把旧数据 drop 掉</span> mongorestore --archive<span class="o">=</span>/tmp/mongodb.gz --gzip --db <span class="nb">test</span> --drop <span class="c"># 利用管道机制从标准输入中恢复数据库,相当于远程导入本地</span> mongodump --archive --db <span class="nb">test</span> --host &lt;remoteip&gt; --gzip| mongorestore --archive --host localhost --drop --gzip</code></pre></figure> Wed, 07 Sep 2016 16:54:18 +0000 https://alwayswithme.github.io/jekyll/update/2016/09/07/mongodb-notes.html https://alwayswithme.github.io/jekyll/update/2016/09/07/mongodb-notes.html jekyll update ArchLinux 搭建安卓开发环境 <p>Android-Studio 和相关 SDK 直接从包管理器 yaourt 下载,让系统中所有用户都能使用</p> <h3 id="准备">准备</h3> <ul> <li>64位系统, Pacman 先开启 <a href="https://wiki.archlinux.org/index.php/Multilib">multilib</a> 需要用到相关的包</li> <li>为了让 Android-Studio 字体美观, 安装 <a href="https://wiki.archlinux.org/index.php/Infinality#Installation_2">infinality-bundle</a> 和打过 infinality 补丁的 jdk8-openjdk-infinality</li> </ul> <h3 id="配置">配置</h3> <h4 id="安装-yaourt">安装 <a href="https://wiki.archlinux.org/index.php/Yaourt">Yaourt</a></h4> <p>用 yaourt 安装软件包时需要打包,开启 xz 的多线程压缩加快打包过程 开启方法: 编辑 <code class="highlighter-rouge">/etc/makepkg.conf</code> 修改其中一项</p> <div class="highlighter-rouge"><pre class="highlight"><code># /etc/makepkg.conf [...] COMPRESSXZ=(xz -T 0 -c -z -) [...] </code></pre> </div> <p>减少与 yaourt 的交互</p> <figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># 复制配置文件</span> cp /etc/yaourtrc ~/.yaourtrc <span class="c"># 编辑文件取消下列两项的注释并修改值</span> <span class="nv">BUILD_NOCONFIRM</span><span class="o">=</span>1 <span class="nv">EDITFILES</span><span class="o">=</span>0</code></pre></figure> <h4 id="翻墙">翻墙</h4> <p>aur 中部分包下载也需要翻墙, 使用 Shadowsocks 只需在终端通过修改环境变量让 yaourt 走代理</p> <div class="highlighter-rouge"><pre class="highlight"><code>export http_proxy=socks5://127.0.0.1:1080 https_proxy=socks5://127.0.0.1:1080 </code></pre> </div> <h3 id="需要安装的包">需要安装的包</h3> <div class="highlighter-rouge"><pre class="highlight"><code>yaourt -S aur/android-bash-completion \ aur/android-google-apis \ aur/android-google-apis-x86 \ aur/android-google-repository \ aur/android-platform \ aur/android-sdk \ aur/android-sdk-build-tools \ aur/android-sdk-platform-tools \ aur/android-sources \ aur/android-studio \ aur/android-support \ aur/android-support-repository </code></pre> </div> <p>陷入漫长等待</p> <h3 id="android-studio-初始启动">Android-Studio 初始启动</h3> <p>让 <code class="highlighter-rouge">/opt/android-sdk/</code> 对 sudoer 可写</p> <figure class="highlight"><pre><code class="language-sh" data-lang="sh">chown -R :wheel /opt/android-sdk/ chmod -R g+w /opt/android-sdk/</code></pre></figure> <p>打开 Android-Studio , 初始启动, 选 custom 将 sdk 下载目录改为 /opt/android-sdk/, 再返回选择 standard ,一直 next 直到 finish. 随便创建一个项目,还要下载 gradle ,再次陷入漫长等待。</p> <h3 id="模拟器硬件加速">模拟器硬件加速</h3> <p>安装 kvm 相关的包</p> <figure class="highlight"><pre><code class="language-sh" data-lang="sh">sudo pacman -S qemu libvirt</code></pre></figure> <p>给模拟器添加相关的选项 <a href="http://developer.android.com/tools/devices/emulator.html#vm-linux">see also</a></p> Wed, 12 Aug 2015 21:33:10 +0000 https://alwayswithme.github.io/jekyll/update/2015/08/12/setup-android-in-archlinux.html https://alwayswithme.github.io/jekyll/update/2015/08/12/setup-android-in-archlinux.html jekyll update