﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>语源科技BlogJava-知行天下</title><link>http://www.blogjava.net/inter12/</link><description /><language>zh-cn</language><lastBuildDate>Mon, 06 Apr 2026 21:53:55 GMT</lastBuildDate><pubDate>Mon, 06 Apr 2026 21:53:55 GMT</pubDate><ttl>60</ttl><item><title>个人博客迁移</title><link>http://www.blogjava.net/inter12/archive/2015/03/22/423722.html</link><dc:creator>薛赵明</dc:creator><author>薛赵明</author><pubDate>Sun, 22 Mar 2015 08:27:00 GMT</pubDate><guid>http://www.blogjava.net/inter12/archive/2015/03/22/423722.html</guid><wfw:comment>http://www.blogjava.net/inter12/comments/423722.html</wfw:comment><comments>http://www.blogjava.net/inter12/archive/2015/03/22/423722.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/inter12/comments/commentRss/423722.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/inter12/services/trackbacks/423722.html</trackback:ping><description><![CDATA[今天无聊在google中搜索了自己的名字，发现排名第一的居然是自己在blogjava上的博客。<br />说明blogjava在google引擎中的排名确实非常高，不过这个博客中一些文章资料也确实很好，是个人见过最好的。<br />不过自己已经将博客地址迁移到自己新的个人地址上了： inter12.org&nbsp;<img src ="http://www.blogjava.net/inter12/aggbug/423722.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/inter12/" target="_blank">薛赵明</a> 2015-03-22 16:27 <a href="http://www.blogjava.net/inter12/archive/2015/03/22/423722.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一次线上jboss僵死问题分析</title><link>http://www.blogjava.net/inter12/archive/2012/09/17/387938.html</link><dc:creator>薛赵明</dc:creator><author>薛赵明</author><pubDate>Mon, 17 Sep 2012 13:09:00 GMT</pubDate><guid>http://www.blogjava.net/inter12/archive/2012/09/17/387938.html</guid><wfw:comment>http://www.blogjava.net/inter12/comments/387938.html</wfw:comment><comments>http://www.blogjava.net/inter12/archive/2012/09/17/387938.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/inter12/comments/commentRss/387938.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/inter12/services/trackbacks/387938.html</trackback:ping><description><![CDATA[<div>问题再现：</div><div>&nbsp; 个人中心在上周四上线(2012.9.13)第一次上线，由于种种缘由，遗留了部分低级别的BUG，后于次日修复，下午4时再次上线。</div><div>&nbsp; 当日晚上8点，运维发现user-web 五台服务器中四台jboss僵死，无法响应用户请求。</div><div></div><div>问题分析：</div><div>&nbsp; 问题发生当日，运维截留了当时的日志信息并dump 了JVM内存信息，关键信息如下：</div><div>&nbsp;&nbsp;</div><div>&nbsp; java.lang.NullPointerException</div><div>Message<span style="white-space:pre">	</span>2012-09-15 02:47:56,992 - com.dianping.userweb.service.processor.MyInfoFavouriteModuleProcessor -235970 [myInfoThreadPoolTaskExecutor-1] ERROR - com.dianping.userweb.service.processor.MyInfoFavouriteModuleProcessor process module exception.</div><div></div><div>&nbsp; ......</div><div>&nbsp; &nbsp;</div><div>&nbsp; "http-0.0.0.0-8080-271" daemon prio=10 tid=0x00002b6ef4211000 nid=0x292a waiting on condition [0x00002b6eac592000]</div><div>&nbsp; &nbsp;java.lang.Thread.State: WAITING (parking)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; at sun.misc.Unsafe.park(Native Method)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; - parking to wait for &nbsp;&lt;0x00000007c4a3f0f0&gt; (a java.util.concurrent.CountDownLatch$Sync)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; at java.util.concurrent.locks.LockSupport.park(LockSupport.java:158)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:969)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1281)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:207)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; at com.dianping.userweb.framework.concurrent.ConcurrentSidebarManager.execute(ConcurrentSidebarManager.java:46)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; at com.dianping.userweb.action.page.myinfo.MyInfoIndexAction.sideBarManagerExecute(MyInfoIndexAction.java:39)</div><div>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</div><div>&nbsp; &nbsp;......</div><div>&nbsp; &nbsp;</div><div>&nbsp; &nbsp;从以上日志中我们可以得到两个信息</div><div>&nbsp; &nbsp;1.一个 process报了空指针异常，</div><div>&nbsp; &nbsp;2.一个线程一直处在waiting状态。</div><div>&nbsp; &nbsp;</div><div>&nbsp; &nbsp;根据日志找到对应的代码：</div><div>&nbsp; &nbsp;</div><div>&nbsp; &nbsp;</div><div>&nbsp; &nbsp; &nbsp;@Override</div><div>&nbsp; &nbsp; public void execute(final UCActionContext context) throws ProjectException {</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; final CountDownLatch countDownLatch = new CountDownLatch(getHeadSize() + getLeftSize() + getRightSize());</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; barHandleExecute(context, countDownLatch, sideBar);</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; try {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; countDownLatch.await(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;--- A&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; } catch (InterruptedException e) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; logger.error("current handle error!", e);</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new ProjectException("current handle error!");</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; }</div><div>&nbsp; &nbsp;&nbsp;</div><div>&nbsp; &nbsp; 这段是处理并行的核心代码，通过CountDownLatch来处理并行的执行，主线程通过 countDownLatch.await();等待子线程的完成。这个方法会抛除InterruptedException异常，这个异常包含三个含义：</div><div>&nbsp; &nbsp;&nbsp;</div><div>&nbsp; &nbsp; 1.这是一个异常，需要业务根据时间情况处理，当然有些废话</div><div>&nbsp; &nbsp; 2.这是一个等待方法，等待的时间不根据CPU或是内存的情况，而是依赖于定时器或是磁盘IO的返回，本案是根据子线程的执行情况</div><div>&nbsp; &nbsp; 3.这个等待线程是可以中断的</div><div>&nbsp; &nbsp; 所以当一个请求过来时，主线程会在这个A处等待，直到所有子线程告诉我，所有子线程已经执行完成了。</div><div>&nbsp; &nbsp;&nbsp;</div><div>&nbsp; &nbsp; 再看barHandleExecute方法的处理：</div><div>&nbsp; &nbsp;&nbsp;</div><div>&nbsp; &nbsp; &nbsp;protected void barHandleExecute(final UCActionContext context, final CountDownLatch countDownLatch, SideBar sideBar) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp;.......&nbsp;</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; threadPoolTaskExecutor.execute(new Runnable() {</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Override</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; public void run() {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Transaction t = Cat.getProducer().newTransaction("Modules",</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;moduleProcessor.getClass().getName());// 接入cat</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; moduleProcessor.execute(context);</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; countDownLatch.countDown();</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.setStatus(Transaction.SUCCESS);</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } catch (Throwable e) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Cat.getProducer().logError(e);// 用log4j记录系统异常，以便在Logview中看到此信息</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.setStatus(e);</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } finally {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.complete(); &nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });</div><div>&nbsp; &nbsp; &nbsp; .......</div><div>&nbsp; &nbsp; }</div><div>&nbsp; &nbsp;&nbsp;</div><div>&nbsp; &nbsp; 对于多个线程，通过线程池来处理，每个线程也就是moduleProcessor处理完成后， countDownLatch.countDown();告诉主线程我已经执行完成了，当主线程收到所有子线程的答复后，继续往下执行，进行页面的渲染。</div><div>&nbsp; &nbsp;&nbsp;</div><div>&nbsp; &nbsp; 以上都是正常流程的流转过程，但是，若我们其中的一个moduleProcessor抛出异常后，会出现什么情况呢？这个异常会被接入CAT监控的代码会吃掉，告诉CAT一个日志信息，然后就什么都不做了。那countDownLatch.countDown();这段代码就永远不会被执行，而主线程又依赖于子线程通过countDownLatch.countDown();来告知是否执行完了。这个时候就悲剧了，主线程一直在等待子线程的消息，无法渲染页面返回结果，这个时候用户的第一反应会是是不是网络原因导致没有加载数据，不停的F5，每次都会产生一个新的请求进来，若是刷新的人越多，这些进程就永远僵死在那边，不释放内存，造成系统雪崩。</div><div>&nbsp; &nbsp;&nbsp;</div><div>&nbsp; &nbsp; 一个合理的介入CAT方式是：</div><div>&nbsp; &nbsp; &nbsp;<span style="white-space:pre">										</span>Transaction t = Cat.getProducer().newTransaction("Modules",</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;moduleProcessor.getClass().getName());// 接入cat</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 业务处理</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.setStatus(Transaction.SUCCESS);</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } catch (Throwable e) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Cat.getProducer().logError(e);&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.setStatus(e);</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new ProjectException(); &nbsp;//这里必须抛出异常，有业务系统决定如何处理异常</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } finally {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.complete(); &nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp;&nbsp;</div><div>几个疑问：</div><div></div><div>1.为什么processor会抛出异常？</div><div>当时在porcessor编写的时候，约定各个processor需要cacth自己的异常，不能因为自己的失败导致请求的其他模块加载失败。我们看看报空指针的地方：</div><div></div><div>.....</div><div>context.getParam(DefaultActionContext.Contant.MEMBER_ID.name)</div><div>.....</div><div>try</div><div>...</div><div></div><div>报错的是从上下文获取memberID的代码，这个操作并没有放在try catch块中。由于个人中心的所有页面都必须依赖于memberID这个字段，所以编码时就忽略了memberId为空的这种情况，而且在我们的action层也对memberID为空的情况作了处理，具体代码如下：</div><div>......</div><div>private void initMemberInfo(UCActionContext context) throws ProjectException {</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; // 验证会员存在</div><div>&nbsp; &nbsp; &nbsp; &nbsp; if (memberId == null) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throwException(MemberUtil.MEMBERID_INVALID_TITLE, MemberUtil.MEMBERID_INVALID_MSG);</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>......&nbsp;</div><div></div><div>所以理论上来说，对于memberId为空的情况不会进入后端处理的processor，所以后端也就没有考虑到从context获取memberID会为空，但是实际情况还是发生了，这段代码也做了拦截，并抛出了异常，不过 在initMemberInfo这个方法的外层，又catch住了这个异常：</div><div></div><div>try</div><div>&nbsp; ....</div><div>&nbsp; &nbsp;initMemberInfo(context);</div><div>&nbsp; &nbsp;</div><div>catch(){</div><div>&nbsp; ....</div><div>}</div><div></div><div>所以说ACTION做的处理并没有起到拦截作用，导致这个null的memberid传递到了后端，后端出错后被接入CAT的代码抓住，导致主线程一直得不到返回。</div><div></div><div>2.为什么在压力测试过程中没有发生这个问题？</div><div>压力测试过程时，当时批量处理方法barHandleExecute的源代码如下所示，并没有介入CAT监控代码</div><div></div><div>&nbsp;protected void barHandleExecute(final UCActionContext context, final CountDownLatch countDownLatch, SideBar sideBar) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp;.......&nbsp;</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; threadPoolTaskExecutor.execute(new Runnable() {</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Override</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; public void run() {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; moduleProcessor.execute(context);</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; countDownLatch.countDown();</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });</div><div>&nbsp; &nbsp; &nbsp; .......</div><div>&nbsp; &nbsp; }</div><div>这个地方为什么没有做try catch，是考虑到我们一些并行的processor存在一些依赖关系，processorA出异常的话，processorB就算不出问题，也不渲染页面。所以这里就不cacth异常，由具体的processor来决定是否cacth异常。所以在压力测试过程，就算其中processor抛出异常，框架就认为这是某个依赖processor抛出的异常，就将这个异常抛到WEB，由最外层做控制，跳转到统一的错误页面。所以这主线程僵死的情况就不会出现。</div><div></div><div>3.周四上线的时候为什么没有让jboss僵死？</div><div>个人中心周四晚上9点多上线，当时访问量也不是很大，就算碰到会导致一些无效的memberID的请求进入，也不会立刻让JBOSS僵死。同时我们在周五下午又对个人中心做了一次上线，重启了应用了，所以没报出这个问题，但是在周五晚上，请求增大，各种复杂情况的请求进入后，就触发了我们代码中的缺陷，最终导致JBOSS僵死。</div><div></div><div>总结：</div><div>1.前段action拦截不合理，放入了memberId为NULL的请求，打开了系统出错的门。</div><div>2.后端processor信任前段过来的数据，并没有做防御式处理，导致空指针异常，同时又没有将这段获取数据的代码纳入try中，进一步将系统推向奔溃边缘。</div><div>3.上线前接入CAT代码，没有考虑全面，这段代码在自己本分(记录系统执行开销及异常)外干了不是它的事，cacth住了我们的系统异常，这个才是压垮系统的最后一根稻草，导致系统雪崩。</div><div>4.框架本身考虑不够全面，用一种靠扩展对自身的约束来处理异常情况，没有分开的去处理：多个无关porcessor和多个存在关联processor的两种情况。</div><div></div><div>以上四步，只要不走错一步，就不会出现周五晚上的JBOSS僵死问题。可见一个重大错误大多是由一系列的错误连锁导致的。</div><div></div><div></div><div></div><div>&nbsp; &nbsp;&nbsp;</div><div>&nbsp; &nbsp;&nbsp;</div><div>&nbsp; &nbsp;</div><div>&nbsp; &nbsp;</div><div>&nbsp; &nbsp;</div><div>&nbsp;&nbsp;</div><img src ="http://www.blogjava.net/inter12/aggbug/387938.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/inter12/" target="_blank">薛赵明</a> 2012-09-17 21:09 <a href="http://www.blogjava.net/inter12/archive/2012/09/17/387938.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一次性能调优笔记(线程池)</title><link>http://www.blogjava.net/inter12/archive/2012/09/12/387837.html</link><dc:creator>薛赵明</dc:creator><author>薛赵明</author><pubDate>Wed, 12 Sep 2012 13:17:00 GMT</pubDate><guid>http://www.blogjava.net/inter12/archive/2012/09/12/387837.html</guid><wfw:comment>http://www.blogjava.net/inter12/comments/387837.html</wfw:comment><comments>http://www.blogjava.net/inter12/archive/2012/09/12/387837.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/inter12/comments/commentRss/387837.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/inter12/services/trackbacks/387837.html</trackback:ping><description><![CDATA[
              <p>普通的性能调优主要从四个方面入手</p>
<p>网络，磁盘IO，内存，CPU四个方面入手，下面案例就是从这四个角度来看。</p>
<p> </p>
<p>我们的页面每天PV在30W ，主要是分布在两个主要页面：个人主页，展示主页。假设每个页面各自承担50%的PV，假设访问时间集中在白天8小时，平均下来每秒的请求数是 5.2个，考虑到高峰情况，那么我们就乘以系数20, 就当100个处理，我们最大的一个请求会产生13个processor ,也就是说 最大产生的线程回事 13*100 = 1300。也就是说高峰时刻会有1300个线程需要被处理，我们将队列设置为1000，对于高峰情况就抛弃，因为若是为了满足高峰情况的需要，就会使得部分请求处在队列中，不能充分的利用CPU的资源。</p>
<p> </p>
<p>在做压力测试时候，自身应用内部做了小的多线程处理的框架，内部采用的线程池是 SPRING的 ThreadPoolTaskExecutor 的类，通过自身的一个监控框架我们发现，所有的线程单元执行的很快，但是在最后组装processor的时候，花费了时间，在过程中观察CPU的利用率并不是很高。</p>
<p>所以预估是在等待所有线程执行完成时，也就是说有大量的processor在线程池的等待队列中，为了验证是否由于该原因造成的，所以做如下测试：</p>
<p> </p>
<p>因为前面提到每秒的平均请求是5.2 考虑到一般的情况，就设置为压测的并发数为 3*5 = 15.</p>
<p> </p>
<p><span style="color: #ff0000; font-size: medium;">测试案例一：</span></p>
<p> </p>
<p>15线程 </p>
<p>循环100次</p>
<p>线程池：</p>
<p> corePoolSize ： CPU = 4 </p>
<p> maxPoolSize  ： 2 * corePoolSize = 8 </p>
<p> queueCapacity ： 1000 </p>
<p> </p>
<p>压测页面： /xxx/22933 </p>
<p> </p>
<p>---------------------------------------------- 这个是分割线 ----------------------------------------------</p>
<p>稳定情况下的线程数：</p>
<p>[root@host_77-69 ~]# ps -eLf | grep java  | wc -l </p>
<p>229</p>
<p>主要是观察，是否充分利用了CPU核数，达到我们预期的效果。现在的服务继承很多框架或是模块后，会启动很多你不知道的线程，在那跑着，时不时跳出来干扰你下，所以一定要等系统运行稳定后观察这个数值。</p>
<p>---------------------------------------------- 这个是分割线 ----------------------------------------------</p>
<p>CPU的一些信息：</p>
<pre class="java" name="code">[root@host_77-69 ~]# vmstat -ns 3  
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0   2056 723528 392024 1330728    0    0     0     1    1    2  0  0 100  0  0	
 0  0   2056 723404 392024 1330728    0    0     0     0  467  806  0  0 100  0  0	
 0  0   2056 723404 392028 1330728    0    0     0    17  462  807  0  0 100  0  0	
 0  0   2056 723404 392028 1330728    0    0     0     0  457  814  0  0 100  0  0</pre>
  
<p>主要是关注 in ， cs 这两个参数</p>
<p>in：每秒软中断次数</p>
<p>cs: 每秒上下文切换的次数</p>
<p> </p>
<p>因为操作系统本身会有一些操作，在未压测前的数值基本在 460 .800 左右。</p>
<p>---------------------------------------------- 这个是分割线 ----------------------------------------------</p>
<p> </p>
<pre class="java" name="code">[root@host_77-69 ~]#  mpstat -P ALL 5 
Linux 2.6.32-71.el6.x86_64 (host_77-69) 	09/12/2012 	_x86_64_	(4 CPU)

02:04:21 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
02:04:26 PM  all    0.10    0.00    0.00    0.00    0.00    0.00    0.00    0.00   99.90
02:04:26 PM    0    0.20    0.00    0.00    0.00    0.00    0.00    0.00    0.00   99.80</pre>
  
<p>关注soft 这个参数 这个代表当前CPU在所有时间中，用于切换上下所化时间比,若是花费的越多，代表当前的线程切换过于频繁，没有充分利用CPU，建议减少线程数或是增加CPU。</p>
<p>user 、 nice、sys主要是观察系统中是否存在大量计算，或是死循环的情况，来消耗大量的CPU。</p>
<p>这个命令是对于vmstat的补充，更直观的观察CPU的上下文切换及软中断情况。</p>
<p> </p>
<p>---------------------------------------------- 下面是内存的初始情况了 ----------------------------------------------</p>
<p>JVM自身的内存情况：</p>
<p> </p>
<pre class="java" name="code">[root@host_77-69 ~]# jstat -gcutil `jps | grep -i main | awk '{print $1}'` 3000 1000 
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  90.91  13.56  60.25     26    0.877     2    0.802    1.679
  0.00   0.00  91.17  13.56  60.25     26    0.877     2    0.802    1.679
  0.00   0.00  91.27  13.56  60.25     26    0.877     2    0.802    1.679
  0.00   0.00  91.28  13.56  60.25     26    0.877     2    0.802    1.679</pre>
 
<p>fugcc次数基本不变，而且各个代内存变化基本不大 </p>
<p> </p>
<p>操作系统的内存情况：</p>
<p> </p>
<pre class="java" name="code">[root@host_77-69 releases]# for i in {1..10};do  free;sleep 3  ; done;
             total       used       free     shared    buffers     cached
Mem:       3925596    3223996     701600          0     392352    1330896
-/+ buffers/cache:    1500748    2424848
Swap:      4194296       2056    4192240
             total       used       free     shared    buffers     cached
Mem:       3925596    3223996     701600          0     392352    1330896
-/+ buffers/cache:    1500748    2424848
Swap:      4194296       2056    4192240
             total       used       free     shared    buffers     cached
Mem:       3925596    3223996     701600          0     392352    1330896
-/+ buffers/cache:    1500748    2424848
Swap:      4194296       2056    4192240
</pre>
  
<p>数值也基本保持不变化</p>
<p> </p>
<p>---------------------------------------------- 下面是磁盘IO的初始情况了 ---------------------------------------------- </p>
<p> </p>
<pre class="java" name="code">[root@host_77-69 ~]# for i in {1..10};do  iostat ; sleep 3 ; done ;
Linux 2.6.32-71.el6.x86_64 (host_77-69) 	09/12/2012 	_x86_64_	(4 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.10    0.00    0.02    0.00    0.00   99.88

Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
sda               0.31         0.33         5.93    1751462   31740872

Linux 2.6.32-71.el6.x86_64 (host_77-69) 	09/12/2012 	_x86_64_	(4 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.10    0.00    0.02    0.00    0.00   99.88

Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
sda               0.31         0.33         5.93    1751462   31740960</pre>
  
<p>主要观察 </p>
<p>Blk_read/s    每秒中读取的块数</p>
<p>Blk_wrtn/s    每秒中写的块数</p>
<p>%iowait       当前在等待磁盘IO的情况</p>
<p> </p>
<p>---------------------------------------------- 说了这么多终于要开始了 ---------------------------------------------- </p>
<p> </p>
<p>开始压测后，得到下面的数据：</p>
<p> </p>
<pre class="java" name="code">[root@host_77-69 ~]# vmstat -ns 3  
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
0  0   2056 698224 393212 1331344    0    0     0     3  536  867  0  0 100  0  0	
 3  0   2056 694796 393212 1332248    0    0     0    19 7170 7515 55  4 40  0  0	
 1  0   2056 694796 393212 1333132    0    0     0     4 7121 7627 50  5 45  0  0	
 4  0   2056 692936 393216 1334376    0    0     0    17 6478 8738 54  5 42  0  0	
 2  0   2056 691548 393232 1335620    0    0     0    25 6497 7663 48  4 48  0  0	
 5  0   2056 689936 393232 1337052    0    0     0     3 7597 7174 47  5 48  0  0	
 3  0   2056 688704 393232 1338496    0    0     0    12 7369 8774 49  5 45  0  0	
 3  0   2056 686348 393232 1341528    0    0     0   819 12298 16011 50  5 45  0  0	
 4  0   2056 684976 393236 1343020    0    0     0    12 6034 6951 48  4 48  0  0	
 3  0   2056 664268 393240 1344508    0    0     0     1 6731 9584 52  5 42  0  0	
 1  0   2056 659360 393240 1346284    0    0     0    12 7797 8431 54  5 41  0  0	
 2  0   2056 657624 393240 1347564    0    0     0  2684 6908 7656 50  5 45  0  0	</pre>
  
<p>在压测的这个过程中，CPU大量上下文切换动作明显增加了很多。</p>
<p> </p>
<pre class="java" name="code">[root@host_77-69 ~]#  mpstat -P ALL 5 
 04:01:32 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
04:01:37 PM  all    0.15    0.00    0.10    0.00    0.00    0.05    0.00    0.00   99.70
04:01:37 PM    0    0.20    0.00    0.00    0.00    0.00    0.20    0.00    0.00   99.60
04:01:37 PM    1    0.20    0.00    0.00    0.00    0.00    0.00    0.00    0.00   99.80
04:01:37 PM    2    0.20    0.00    0.00    0.00    0.00    0.00    0.00    0.00   99.80
04:01:37 PM    3    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00</pre>
 
<p>这个数据上看出其中一个CPU花费在切换的时间比是0.2%，也不是很高。</p>
<p> </p>
<pre class="java" name="code"> [root@host_77-69 ~]# ps -eLf | grep java  | wc -l 
229
[root@host_77-69 ~]# ps -eLf | grep java  | wc -l 
236
[root@host_77-69 ~]# ps -eLf | grep java  | wc -l 
236
[root@host_77-69 ~]# ps -eLf | grep java  | wc -l 
235
[root@host_77-69 ~]# ps -eLf | grep java  | wc -l 
229
[root@host_77-69 ~]# ps -eLf | grep java  | wc -l 
229</pre>
  
<p>java的线程数增加到了236，也就是说增加了7个，我们最初设置是4个，队列1000 ，在队列满了后，增加了3个，也就是说，这种情况，扩容到7个线程，就能满足我们的压测条件，那说明core为4，存在大量的队列积压情况，同时，上面的数据表明，用于上下文切换的比例也不是很高，如此我们就可以增加线程池的corePoolSize。那么下个案例就可以设置为8个试试看。</p>
<p> </p>
<pre class="java" name="code">[root@host_77-69 ~]# jstat -gcutil `jps | grep -i main | awk '{print $1}'` 3000 1000 
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT   
 0.00  27.46  76.94  19.37  60.86     31    1.139     3    1.497    2.636
  0.00  23.34  85.64  19.37  60.90     33    1.150     3    1.497    2.647
  0.00  36.14  38.44  19.37  60.91     35    1.167     3    1.497    2.665
  0.00  63.19  37.87  19.37  60.92     37    1.191     3    1.497    2.688
 59.29   0.00   1.61  19.48  60.92     40    1.226     3    1.497    2.723
  0.00  50.63  58.22  19.50  60.93     41    1.236     3    1.497    2.733
  0.00  51.09  70.36  19.58  60.94     43    1.265     3    1.497    2.762
 44.05   0.00   2.09  19.67  60.95     46    1.298     3    1.497    2.795
  0.00  83.74  75.70  19.68  60.96     47    1.316     3    1.497    2.813
  0.00  89.57  77.32  20.21  60.96     49    1.350     3    1.497    2.847
 46.02   0.00  36.83  22.12  60.97     52    1.399     3    1.497    2.896
 36.69   0.00  37.78  22.12  60.98     54    1.417     3    1.497    2.914
 59.51   0.00  23.47  22.12  61.00     56    1.435     3    1.497    2.932
 64.53   0.00  36.51  22.29  61.03     58    1.461     3    1.497    2.959
 73.19   0.00  78.00  23.01  61.05     60    1.497     3    1.497    2.995
 54.24   0.00  36.10  23.01  61.06     62    1.521     3    1.497    3.018
 79.36   0.00  82.65  23.01  61.08     64    1.547     3    1.497    3.044
  0.00  68.75  48.34  26.61  61.10     67    1.606     3    1.497    3.103
 29.33   0.00  93.75  26.61  61.12     68    1.613     3    1.497    3.110
  0.00  45.32  23.68  26.61  61.12     71    1.640     3    1.497    3.138
 34.93   0.00  19.75  29.84  61.13     74    1.697     3    1.497    3.194
 22.59   0.00  27.47  29.84  61.14     76    1.711     3    1.497    3.208
 54.40   0.00  74.16  30.45  61.15     78    1.734     3    1.497    3.231
 25.23   0.00  77.50  30.45  61.15     80    1.747     3    1.497    3.245
 25.23   0.00  98.39  30.45  61.15     80    1.747     3    1.497    3.245
 25.23   0.00  99.94  30.45  61.15     80    1.747     3    1.497    3.245
  0.00  14.32   1.42  30.45  61.15     81    1.752     3    1.497    3.250
  0.00  14.32   2.15  30.45  61.15     81    1.752     3    1.497    3.250
  0.00  14.32   2.27  30.45  61.15     81    1.752     3    1.497    3.250
  0.00  14.32   2.48  30.45  61.15     81    1.752     3    1.497    3.250</pre>
<p> </p>
<p> </p>
<p>这个是查看JVM的GC情况的，数据表明，压测期间，ygc还是蛮频繁，但是每次ygc后进入old区的数据并不是很多，说明我们的应用大部分是朝生夕死，并不会发生频繁fgc的情况，之后就不用把重心放在JVM的GC上。</p>
<p> </p>
<pre class="java" name="code">[root@host_77-69 releases]# for i in {1..10};do  free;sleep 3  ; done;
             total       used       free     shared    buffers     cached
Mem:       3925596    3370968     554628          0     395584    1369572
-/+ buffers/cache:    1605812    2319784
Swap:      4194296       2056    4192240</pre>
 
<p>操作系统跟之心前相比，基本没有发生任何的改变。</p>
<p> </p>
<pre class="java" name="code">avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.10    0.00    0.02    0.00    0.00   99.88

Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
sda               0.31         0.33         5.95    1751462   31823032

Linux 2.6.32-71.el6.x86_64 (host_77-69) 	09/12/2012 	_x86_64_	(4 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.10    0.00    0.02    0.00    0.00   99.88

Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
sda               0.31         0.33         5.95    1751462   31823032</pre>
<p> </p>
<p> </p>
<p>这个是当前应用对于磁盘的消耗情况，对比初始值，基本没什么变化，可以看出我们这个应用没有磁盘IO的消耗，这说明本应用并没有大量的操作本地磁盘IO情况。</p>
<p>这个也不是导致我们系统慢的原因，也可以排除。</p>
<p> </p>
<p> </p>
<p>xxxModuleProcessor<span style="white-space: pre;">	</span>33<span style="white-space: pre;">	</span> 最慢的processor是33毫秒</p>
<p> </p>
<p>我们的processor最大的消耗是33毫秒，外部调用4.9ms ，但是最后看到的消耗时间是557ms，且上面的情况说明了，存在大量队列积压，我们的数据处理processor都在等待队列了</p>
<p> </p>
<p>下面是我们通过</p>
<p>Avg(ms):</p>
<p>第一次： 662.5 毫秒</p>
<p>第二次： 680   毫秒</p>
<p>第三次： 690   毫秒</p>
<p> </p>
<p>通过上面的分析，平均响应时间是：680ms,基本可以确定问题在于线程池corePoolSize过小,导致了一定的数据在队列中积压。</p>
<p> </p>
<p> </p>
<p>---------------------------------------------  这是一条伟大的分割线  ---------------------------------------------</p>
<p><span style="color: #ff0000; font-size: medium;">测试案例二：</span></p>
<p> </p>
<p>改动：增加一倍的corePoolSize</p>
<p> </p>
<p>15线程 </p>
<p>循环100次</p>
<p>线程池：</p>
<p> corePoolSize ；2 * CPU =  8 </p>
<p> maxPoolSize  ：2 * corePoolSize = 16 </p>
<p> queueCapacity ： 1000 </p>
<p> </p>
<p>压测页面： /member/22933 </p>
<p> </p>
<p>---------------------------------------------  我又出现了  ---------------------------------------------</p>
<p> </p>
<p>再次启动稳定后：</p>
<p>[root@host_77-69 ~]# for i in {1..10000};do    ps -eLf | grep java  | wc -l; echo "-------" ; sleep 2 ; done;</p>
<p>215</p>
<p>-------</p>
<p>215</p>
<p>-------</p>
<p>215</p>
<p>-------</p>
<p> </p>
<p>java的线程数维持在215个，跟上面有点不同，当然不管了，这个不是重点。</p>
<p> </p>
<p> </p>
<pre class="java" name="code">[root@host_77-69 ~]# vmstat -ns 3  
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
 0  0   2056 933420 395768 1341376    0    0     0     8  579  875  0  0 100  0  0	
 0  0   2056 933420 395768 1341376    0    0     0     3  579  860  0  0 100  0  0	
 0  0   2056 933420 395776 1341372    0    0     0     9  568  867  0  0 100  0  0	</pre>
 
<p> </p>
<p>初始情况CPU运行都很正常 </p>
<p> </p>
<p> </p>
<pre class="java" name="code">[root@host_77-69 ~]#  mpstat -P ALL 5 
Linux 2.6.32-71.el6.x86_64 (host_77-69) 	09/12/2012 	_x86_64_	(4 CPU)

05:43:34 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
05:43:39 PM  all    0.40    0.00    0.10    0.00    0.00    0.00    0.00    0.00   99.50
05:43:39 PM    0    0.80    0.00    0.20    0.00    0.00    0.00    0.00    0.00   99.00</pre>
<p> </p>
<p>基本没有软中断</p>
<p> </p>
<p>压测后得到如下数据：</p>
<p>[root@host_77-69 ~]# for i in {1..10000};do    ps -eLf | grep java  | wc -l; echo "-------" ; sleep 2 ; done;</p>
<p>214</p>
<p>-------</p>
<p>214</p>
<p>-------</p>
<p>214</p>
<p>-------</p>
<p>217</p>
<p>-------</p>
<p>219</p>
<p>-------</p>
<p>219</p>
<p>-------</p>
<p>。。。。。。</p>
<p>221</p>
<p>-------</p>
<p>219</p>
<p>-------</p>
<p>。。。。。。</p>
<p>218</p>
<p>-------</p>
<p>218</p>
<p>-------</p>
<p>214</p>
<p>-------</p>
<p>这个java线程数的变化情况，从 214-- 221 -- 214。 初始化了8个，然后增加了7个，也就是说线程池中总共启用了15个线程。</p>
<p>------------------------------------------------------  </p>
<p> </p>
<pre class="java" name="code">[root@host_77-69 ~]#  mpstat -P ALL 5 
05:59:00 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
05:59:05 PM  all   51.46    0.00    4.58    0.00    0.29    2.00    0.00    0.00   41.67
05:59:05 PM    0   50.98    0.00    4.71    0.00    0.98    7.25    0.00    0.00   36.08
05:59:05 PM    1   51.07    0.00    4.29    0.00    0.00    0.39    0.00    0.00   44.25
05:59:05 PM    2   50.49    0.00    4.87    0.00    0.00    0.19    0.00    0.00   44.44
05:59:05 PM    3   53.29    0.00    4.46    0.00    0.00    0.19    0.00    0.00   42.05

05:59:05 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
05:59:10 PM  all   49.56    0.00    4.25    0.00    0.29    2.00    0.00    0.00   43.89
05:59:10 PM    0   46.56    0.00    3.73    0.00    1.18    7.07    0.00    0.00   41.45
05:59:10 PM    1   58.12    0.00    4.31    0.00    0.00    0.39    0.00    0.00   37.18
05:59:10 PM    2   45.72    0.00    4.67    0.00    0.00    0.39    0.00    0.00   49.22
05:59:10 PM    3   47.95    0.00    4.29    0.00    0.00    0.39    0.00    0.00   47.37

05:59:10 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
05:59:15 PM  all   50.54    0.00    4.19    0.00    0.29    1.75    0.00    0.00   43.23
05:59:15 PM    0   55.36    0.00    3.70    0.00    1.17    5.85    0.00    0.00   33.92
05:59:15 PM    1   53.62    0.00    4.70    0.00    0.00    0.59    0.00    0.00   41.10
05:59:15 PM    2   46.98    0.00    4.29    0.00    0.00    0.19    0.00    0.00   48.54
05:59:15 PM    3   46.21    0.00    4.27    0.00    0.00    0.19    0.00    0.00   49.32

05:59:15 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
05:59:20 PM  all   52.78    0.00    4.48    0.05    0.39    2.14    0.00    0.00   40.17
05:59:20 PM    0   52.17    0.00    3.94    0.00    1.57    7.68    0.00    0.00   34.65
05:59:20 PM    1   52.35    0.00    4.90    0.00    0.00    0.39    0.00    0.00   42.35
05:59:20 PM    2   57.09    0.00    4.85    0.00    0.00    0.19    0.00    0.00   37.86
05:59:20 PM    3   49.42    0.00    4.23    0.00    0.00    0.38    0.00    0.00   45.96

05:59:20 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
05:59:25 PM  all   46.90    0.00    3.85    0.00    0.34    1.76    0.00    0.00   47.15
05:59:25 PM    0   48.34    0.00    3.70    0.00    1.56    6.43    0.00    0.00   39.96
05:59:25 PM    1   43.30    0.00    4.47    0.00    0.00    0.39    0.00    0.00   51.84
05:59:25 PM    2   50.59    0.00    3.52    0.00    0.00    0.39    0.00    0.00   45.51
05:59:25 PM    3   45.14    0.00    3.70    0.00    0.00    0.19    0.00    0.00   50.97</pre>
  
<p>上面的数据表明，中断占CPU的比例确大大增加了。单核中断最高达到了7.25% 如此导致了什么结果呢?</p>
<p> </p>
<p>Min(ms)<span style="white-space: pre;">	</span>Max(ms)<span style="white-space: pre;">	</span>Avg(ms)<span style="white-space: pre;">	</span>95Line(ms)<span style="white-space: pre;">	</span>Std(ms)<span style="white-space: pre;">	</span>TPS</p>
<p>161.2<span style="white-space: pre;">	</span>  8877.4<span style="white-space: pre;">	</span>731.7<span style="white-space: pre;">	</span>  1000.0<span style="white-space: pre;">	</span>    65.3<span style="white-space: pre;">	</span>  1.2</p>
<p> </p>
<p>想比较corePoolSize:4 max:8 性能反而下降了。平均响应时间从662.5降到了731.7。</p>
<p> </p>
<p>最慢的processor的消耗时间是：<span style="white-space: pre;">		</span>187.9<span style="white-space: pre;">	</span></p>
<p> </p>
<p>期间也猜测可能是其中一个服务被我们压坏了，就重启了那个服务，再次压测的结果是</p>
<p>Min(ms)<span style="white-space: pre;">	</span>Max(ms)<span style="white-space: pre;">	</span>Avg(ms)<span style="white-space: pre;">	</span>95Line(ms)<span style="white-space: pre;">	</span>Std(ms)<span style="white-space: pre;">	</span>TPS</p>
<p>102.9<span style="white-space: pre;">	</span>  9188.9<span style="white-space: pre;">	</span>762.5<span style="white-space: pre;">	</span>  1095.0<span style="white-space: pre;">	</span>    657.8<span style="white-space: pre;">	</span>  3.0</p>
<p> </p>
<p>平均响应时间是：750毫秒左右。</p>
<p> </p>
<p><span style="color: #ff0000;">也就是说，基本可以确认是由于我们增加了 coreSize和maxSize导致性能变慢了。慢了近80毫秒。说明过多的CPU并不会加快我们的处理速度。</span></p>
<p>如此就有了下面的方案。</p>
<p> </p>
<p><span style="color: #ff0000; font-size: medium;">测试方案三：</span></p>
<p>corePoolSize : cpu数量 + 1  = 5 </p>
<p>maxPoolSzie : 2 *　corePoolSize　 = 10 </p>
<p> </p>
<p>我们看下具体情况吧：</p>
<p> </p>
<p> </p>
<pre class="java" name="code">[root@host_77-69 ~]#  mpstat -P ALL 5 
06:57:36 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
06:57:41 PM  all   58.27    0.00    5.38    0.00    0.49    2.30    0.00    0.00   33.56
06:57:41 PM    0   61.66    0.00    4.74    0.00    1.98    8.10    0.00    0.00   23.52
06:57:41 PM    1   55.75    0.00    5.65    0.00    0.00    0.58    0.00    0.00   38.01
06:57:41 PM    2   57.81    0.00    5.47    0.00    0.00    0.20    0.00    0.00   36.52</pre>
  
<p>CPU的上下文切换还是很厉害。达到了8.10%</p>
<p>[root@host_77-69 ~]# for i in {1..10000};do    ps -eLf | grep java  | wc -l; echo "-------" ; sleep 2 ; done;</p>
<p>214</p>
<p>-------</p>
<p>214</p>
<p>-------</p>
<p>219</p>
<p>-------</p>
<p>219</p>
<p>-------</p>
<p>217</p>
<p>-------</p>
<p>215</p>
<p>-------</p>
<p>214</p>
<p> </p>
<p>214--219 </p>
<p>原来线程池core是5，我们最大是10个，线程数确实增加到了10个，就是说10个线程对应到4个CPU上，两者的比例是1:2.25 </p>
<p> </p>
<p>结果：</p>
<p>第一次压测是：648毫秒</p>
<p>第二次压测是：622毫秒</p>
<p>第三次压测是：603毫秒</p>
<p> </p>
<p>就取中间值吧：622毫秒 </p>
<p> </p>
<p><span style="color: #ff0000;">性能想比较 core：8 max:16的话，有0.060毫秒的提升。说明一定cpu和进程数保持在1:2.25的比例上，效率上还是有提高的，但是上下文切换的还是很厉害。</span></p>
<p> </p>
<p>为了不让它切换的这么厉害，就将max设置的小点吧。</p>
<p> </p>
<p><span style="color: #ff0000; font-size: medium;">测试方案四</span></p>
<p>线程：15 </p>
<p>循环：100次</p>
<p>corePoolSize : cpu数量 + 1    =  5 </p>
<p>maxPoolSzie : 2 * cpu    =  8</p>
<p> </p>
<pre class="java" name="code">08:13:13 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
08:13:18 PM  all   52.45    0.00    4.60    0.00    0.10    1.27    0.00    0.00   41.58
08:13:18 PM    0   60.00    0.00    3.96    0.00    0.59    3.96    0.00    0.00   31.49
08:13:18 PM    1   50.29    0.00    5.48    0.00    0.00    0.20    0.00    0.00   44.03
08:13:18 PM    2   50.78    0.00    4.86    0.00    0.00    0.58    0.00    0.00   43.77
08:13:18 PM    3   48.83    0.00    4.28    0.00    0.00    0.19    0.00    0.00   46.69

08:13:18 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
08:13:23 PM  all   50.05    0.00    4.29    0.00    0.10    1.22    0.00    0.00   44.34
08:13:23 PM    0   57.54    0.00    4.56    0.00    0.20    3.97    0.00    0.00   33.73
08:13:23 PM    1   49.81    0.00    4.28    0.00    0.00    0.39    0.00    0.00   45.53
08:13:23 PM    2   48.16    0.00    3.88    0.00    0.00    0.39    0.00    0.00   47.57
08:13:23 PM    3   45.07    0.00    4.45    0.00    0.00    0.19    0.00    0.00   50.29

08:13:23 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
08:13:28 PM  all   51.34    0.00    4.69    0.00    0.10    1.27    0.00    0.00   42.60
08:13:28 PM    0   60.08    0.00    4.15    0.00    0.40    3.95    0.00    0.00   31.42
08:13:28 PM    1   47.75    0.00    6.07    0.00    0.00    0.39    0.00    0.00   45.79
08:13:28 PM    2   47.48    0.00    4.26    0.00    0.00    0.39    0.00    0.00   47.87
08:13:28 PM    3   50.19    0.00    4.28    0.00    0.00    0.39    0.00    0.00   45.14</pre>
<p>中断时间确实从7%下降到了4%左右。</p>
<p>[root@host_77-69 ~]# for i in {1..10000};do    ps -eLf | grep java  | wc -l; echo "-------" ; sleep 2 ; done;</p>
<p>215</p>
<p>-------</p>
<p>217</p>
<p>-------</p>
<p>217</p>
<p>-------</p>
<p>219</p>
<p>-------</p>
<p>219</p>
<p>-------</p>
<p>218</p>
<p>-------</p>
<p>线程池基本处于饱和状态。</p>
<p> </p>
<p>结果：</p>
<p>第一次压测结果：629毫秒</p>
<p>第二次压测结果：618毫秒</p>
<p>第三次压测结果：586毫秒</p>
<p> </p>
<p>这次CPU:线程数为1:2</p>
<p>相比较CPU和线程数1.2.25的结果有稍微的提升，因为CPU中断时间比下降了。</p>
<p> </p>
<p>最终的结论，JVM的垃圾回收，本地磁盘IO，操作系统内存都不会对本应用产生影响，唯一的元素就是线程池大小的设置。<span style="color: #ff0000;">目前测试出来的最佳策略是：</span></p>
<p>corePoolSize = cpu + 1</p>
<p>maxPoolSize = 2 *　
cpu  　</p>
<p> </p>
              
              <br/><br/>
              <span style="color:red;">
                <a href="http://inter12.iteye.com/blog/1677180#comments" style="color:red;">已有 <strong>0</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
              </span>
              <br/><br/><br/>
<span style="color:#E28822;">ITeye推荐</span>
<br/>
<ul><li><a href='/clicks/433' target='_blank'><span style="color:red;font-weight:bold;">—软件人才免语言低担保 赴美带薪读研！— </span></a></li></ul>
<br/><br/><br/>
              <img src ="http://www.blogjava.net/inter12/aggbug/387837.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/inter12/" target="_blank">薛赵明</a> 2012-09-12 21:17 <a href="http://www.blogjava.net/inter12/archive/2012/09/12/387837.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>AOP 的简单入门</title><link>http://www.blogjava.net/inter12/archive/2012/09/08/387838.html</link><dc:creator>薛赵明</dc:creator><author>薛赵明</author><pubDate>Sat, 08 Sep 2012 10:01:00 GMT</pubDate><guid>http://www.blogjava.net/inter12/archive/2012/09/08/387838.html</guid><wfw:comment>http://www.blogjava.net/inter12/comments/387838.html</wfw:comment><comments>http://www.blogjava.net/inter12/archive/2012/09/08/387838.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/inter12/comments/commentRss/387838.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/inter12/services/trackbacks/387838.html</trackback:ping><description><![CDATA[
              <p> </p>
<p><span style="font-size: medium;">AOP 的简单入门</span>
</p>
<p> </p>
<p>自己也算是从业多年，对于AOP的概念应该算是听的烂的不能再烂了，这方面的书也看的不少，但是自己一直没有机会去实践下。</p>
<p>乘在这个稍微有点空闲的下午，就随手玩玩SPRING的AOP，也谈谈自己对于AOP的理解及其衍生的一些东西。</p>
<p> </p>
<p><span style="color: #ff6600; font-size: small;"><span style="font-size: medium;">1.一切术语都是纸老虎</span>
</span>
</p>
<p>基本概念，也可以说是基本术语。任何一个软件概念提出时候，都少不了这个东西。CRM,AOP,SOA等等，伴随这些东西的都会有相应体系内的术语。</p>
<p>我个人的看法是一切术语的出现不是并不是向大众解释清楚这件事到底是怎么一回事，其主要是基于两个方面考虑：</p>
<p> </p>
<p>1.让自己提出观点显得系统化，更具有说服力。</p>
<p>2.迷惑大众，或是迷糊那些刚进入这个领域的初学者。</p>
<p> </p>
<p>两个看似矛盾的因素，其实归结到本质就是将一个简单的东西复杂化并迷糊那些辨别能力的一般的人，大家对于抽象到一定程度的东西但是心怀敬畏之心的，然后带着膜拜的心理去接受，生怕一不小心亵渎了内心的女神。扯开来讲现在社会，官员的道德沦丧，其中的一个诱因就是对于敬畏之心的缺失，当一个人无所畏时，才是最可怕的，因为这个时候已经没有任何约束能约束他的行为。</p>
<p> </p>
<p>回归正题，既然提到术语，那么我们就将AOP中的那些术语列出来看看。</p>
<p>切面（Aspect）、连接点（Joinpoint）、通知（Advice）、切入点（Pointcut） 、目标对象（Target Object）、AOP代理（AOP Proxy）。</p>
<p>通知又分为几种：前置通知（Before advice）、后通知（After advice）、返回后通知（After return advice）、环绕通知（Around advice）、抛出异常后通知（After throwing advice） 等。</p>
<p> </p>
<p>好了，现在我们来看看这些术语，谁能一眼就明白这些东西能告诉我们什么？谁能通畅的理清楚它们之间的关系。开始解释之前，我们看看维基百科上对AOP的定义是什么：</p>
<p> </p>
<pre class="java" name="code">面向侧面的程序设计（aspect-oriented programming，AOP，又译作面向方面的程序设计、觀點導向編程）是计算机科学中的一个术语，指一种程序设计范型。该范型以一种称为侧面（aspect，又译作方面）的语言构造为基础，侧面是一种新的模块化机制，用来描述分散在对象、类或函数中的横切关注点（crosscutting concern）。</pre>
 
<p> </p>
<p>多么简单的解释，就是对于那些在主程序业务之外的事情，我们该怎么设计，看清楚，是程序设计，而不是程序编码，很多地方都将AOP理解成面向切面的编码，那是错误，AOP约定的是一种设计范型，具体到java上的实现例如aspectJ,例如spring的AOP。再具体到实现技术就是JDK自带的动态代理，cglib的字节码修改等等。<span style="color: #ff6600;">AOP != spring AOP ,AOP != cglib 。一个是设计范型，一个是实现。</span>
</p>
<p> </p>
<p>(这里扯开点说，一些所谓的架构师喜欢谈概念，还有人提出架构师更关注抽象的东西，普通的码农更关注具象的东西。这些东西本身没错，在大量的实践之后，我们确实应该去在这大量的实践中归纳，总结出规律，这就是进行抽象。但是，但是，那些只跟你扯抽象而不落地的架构师，还是远离些，因为他们不是基于大量的具象后，进行抽象，他们不过是邯郸学步的抄袭那些真正的架构师的想法，并转变为自己的观点，TMD就是个大忽悠)</p>
<p> </p>
<p><span style="color: #ff6600; font-size: small;"><span style="font-size: medium;">2.那些主程序之外的杂事</span>
</span>
</p>
<p>我们知道，一个程序是会被编译成计算机能识别的机器码，交给机器去执行，那么我们想知道机器在执行我们的代码时候，发生的一些事，例如：</p>
<p> </p>
<ol>
<li>一个输入是否得到我们想要的输出呢？</li>
<li>一个函数执行的时间开销是多少呢？</li>
<li>若是一个良好的程序遇到无法处理的异常我们该怎么办呢？</li>
<li>存在多个数据源，我们需要在多个数据源的更新都完成后，再提交该怎么处理呢？</li>
<li>我们想将一些我们不认可的请求拒绝，那又该怎么处理呢？</li>
</ol>
<p> </p>
<p>当以上的这些杂事出现时，我们该怎么做呢，一种很简单粗暴的方式就是硬编码的将这些杂事写入到我们的主要业务程序中，我想这种方式大家在日常的开发经常能看到，包括你去看一些所谓平台级别的产品也是采用这种方式，笔者现在所在单位的部分产品也是如此。不管你爽不爽，老子爽了就可以了。</p>
<p> </p>
<p> </p>
<p><span style="color: #ff6600; font-size: medium;">3.上吧，骚年</span></p>
<p>用个不恰当的比喻来说：你是个开饭店的，来了很多顾客，当你女服务员在招待顾客时，你突然发现XX院长来了，想来OOXX下。你就定了一个流程，来XX院长，可OOXX，硬编码的方式就是：</p>
<p>1.你女服务员在招待</p>
<p>2.XX院长跟她OOXX</p>
<p>3.你女服务员在招待</p>
<p>4.屌丝来了，无视之</p>
<p>......</p>
<p>这个过程中你不管你女服务员是否来例假，你不关心你女服务员是否今天心情不好。嗯，院长爽了，若是你得到了某种回报，也还好，爽了；若是什么都得不到，那可就欲哭无泪。</p>
<p>如果你足够幸福的话，有另一个口味美女服务员，她会隐藏技能---洗脚。恰巧经管学院院长也来了，这个家伙还喜欢洗脚，那怎么办，那就上吧：</p>
<p>1.你女服务员在招待</p>
<p>2.XX院长跟她OOXX+洗脚</p>
<p>3.你女服务员在招待</p>
<p>4.屌丝来了，无视之</p>
<p>......</p>
<p>如果你生意足够好，恰好XX院长又很多，好吧，你的美女们一直处在XXOO状态中..... 很high，很happy。但是，我们回到最开始，你为什么要招女服务员？找她们来，是因为你需要她们去招待顾客，一个屌丝在等吃饭没关系，若是一群的屌丝在等吃饭，你就悲剧，没人招待屌丝们了，因为你的那些服务员都在跟院长们OOXX中，因为命令已经固化到流程中，你改不了，至少在你修改流程之前。通过我们软件术语来说，就是不能及时、灵活的应对自身内部(你的美女们身体、心情)和外部(屌丝数量)的变化。当然， 你若是铁道部这样的共和国长子，那是没关系的，让那群屌丝们等去吧，因为方圆960万平方公里就此一家，别无分号。</p>
<p> </p>
<p>若你不是，哪天觉得自己有点生意有点扛不住或是那点生殖器破事被某个黑心，吃不到葡萄的院长小弟揭发了，扛不住随之而来的社会舆论压力，不能跟院长们OOXX了，只准对他们笑个，这个时候你得通知那些女服务员，说不准OOXX了，只能看了。若是你只有一家店，还好，自己喊一声，重新打印流程规章表，若是全国连锁的话...... 一桌的杯具摆在你茶几上。</p>
  
<p><span style="color: #ff6600; font-size: small;"><span style="font-size: medium;">4.正义化身的出现</span>
</span>
</p>
<p>好了，扯了这么多，终于要该AOP兄弟出场了，再不出估计戏都散场了。</p>
<p>针对以上的种种问题，我们该怎么处理这些我们店主要生意之外的杂事呢(OOXX),有什么更好的方式来随时应对种种变化。这个就是我们AOP兄弟想干的事情，从主业中剥离出这些杂事，进行单独处理的设计。主业务只关注于自己的领域，对于特殊领域的处理(OOXX),通过侧面来封装维护，这样使得散落在不同口味美女的特殊操作可以很好的管理起来。来段专业点的说明吧：</p>
<p> </p>
<pre class="java" name="code">从主关注点中分离出横切关注点是面向侧面的程序设计的核心概念。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来，业务逻辑的代码中不再含有针对特定领域问题代码的调用，业务逻辑同特定领域问题的关系通过侧面来封装、维护，这样原本分散在在整个应用程序中的变动就可以很好的管理起来。</pre>
 
<p> </p>
<p>好了，AOP的就是个这么简单的东西，别去想那些繁杂的spring配置和概念术语。它只是一种设计范型。</p>
<p> </p>
<p> </p>
<p>绕了这么久，让我们来打倒那些纸老虎吧。</p>
<p>我开饭店，屌丝、院长来吃饭，美女们招待顾客 ，这个是我们的主业。  ========  目标对象（Target Object） 就是店主我，我开了两个店，戏院和饭店</p>
<p>哦，北大院长来饭店吃饭了                                                            ========  切入点（Pointcut） 他们来我戏院看戏的话，不管，直管饭店的事</p>
<p>院长开始吃饭，喝酒了。                                                               ========  连接点（Joinpoint） ，就是我们的一些行为，院长如果来围观的话，无视之，哥是开饭店的。</p>
<p>院长想跟美女们OOXX了                                                               ========  通知（Advice）院长来了，也吃了饭了，那接下来干什么呢？通知就是决定干什么:OOXX或是洗脚</p>
<p>院长除了想OOXX之外，还想洗脚，那么怎么办呢？                           ========  切面（Aspect） ，规定院长来了可以干什么，就是决定可以有多少个通知：OOXX||洗脚 或是 OOXX &amp;&amp;　洗脚</p>
<p>----------------------------------------------------------------------------------------------------</p>
<p>院长想吃饭后洗脚                                                                ======== 后通知（After advice） </p>
<p>院长想吃饭前洗脚                                                                ======== 前置通知（Before advice）</p>
<p>院长想根据吃饭后的心情决定是OOXX还是洗脚                         ======== 返回后通知（After return advice）</p>
<p>院长吃饭吃出脑中风了                                                          ======== 抛出异常后通知（After throwing advice）这个时候有个通知跳出来：打120，送医院！</p>
<p>院长想饭前洗脚，饭后OOXX                                                 ======== 环绕通知（Around advice）</p>
<p> </p>
<p>作为老板的我，应该怎么更好的切入这些洗脚啊，OOXX服务呢    ======== AOP代理（AOP Proxy）怎么在干好招待顾客这件事上切入 洗脚||OOXX</p>
<p> </p>
<p>若是上面的这些你还是看不明白的话，那么我们就具象到spring上，看看到底是件上面事吧。spring中Aspect叫Advisor。Joinpoint叫Weaving。很操蛋，也很让人无语的术语啊</p>
<p> </p>
<p> </p>
<pre class="java" name="code">切面：
package com.zhaming.aop.advice;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

/**
 * 后置通知
 * @author inter12
 *
 */
public class AfterAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("拦截了:" + method.getName() + "方法");
        System.out.println("洗脚");
    }

}


package com.zhaming.aop.advice;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

/**
 * 前置通知
 * 
 * @author inter12
 */
public class BeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {

        System.out.println("拦截了:" + method.getName() + "方法");
        System.out.println("OOXX");
    }

}


package com.zhaming.aop.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * 环绕通知
 * 
 * @author inter12
 */
public class CompareAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object result = null;
        String userName = invocation.getArguments()[0].toString();
        if (null != userName &amp;&amp; userName.equals("yuanzhang")) {
            System.out.println("院长通过---------------");
            result = invocation.proceed();
        } else {
            System.out.println("屌丝拒绝---------------");
        }
        return result;
    }

}

目标对象：
package com.zhaming.aop.restaurant;

public interface RestaurantService {

    public void zhaodaiguke(String userName);

    public void weiguan(String userName);
}

package com.zhaming.aop.restaurant;

/**
 * 目标对象
 * 
 * @author inter12
 */
public class RestaurantServiceImpl implements RestaurantService {

    @Override
    public void zhaodaiguke(String userName) {
        System.out.println("--------- 姑娘们在招待顾客:" + userName);
    }

    @Override
    public void weiguan(String userName) {
        System.out.println(userName + ":在围观");

    }

}

客户端：
package com.zhaming.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.zhaming.aop.restaurant.RestaurantService;

public class Main {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new FileSystemXmlApplicationContext(
                                                                                    "//home/inter12/workspace/Light/src/main/java/appcontext-aop.xml");

        RestaurantService bean = (RestaurantService) applicationContext.getBean("restaurantService");
        bean.zhaodaiguke("yuanzhang");
        bean.zhaodaiguke("diaosi");
    }
}

配置文件：
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"&gt;

	&lt;!-- 通知 --&gt;
	&lt;bean id="beforeAdvice" class="com.zhaming.aop.advice.BeforeAdvice"&gt;&lt;/bean&gt;
	&lt;bean id="afterAdvice" class="com.zhaming.aop.advice.AfterAdvice"&gt;&lt;/bean&gt;
	&lt;bean id="compareAdvice" class="com.zhaming.aop.advice.CompareAdvice"&gt;&lt;/bean&gt;

	&lt;!-- 目标对象 --&gt;
	&lt;bean id="restaurantServiceTarget" class="com.zhaming.aop.restaurant.RestaurantServiceImpl"&gt;&lt;/bean&gt;

	&lt;bean id="restaurantService" class="org.springframework.aop.framework.ProxyFactoryBean"&gt;

		&lt;!-- 拦截那些接口 : 切入点 只关心饭店的事--&gt;
		&lt;property name="proxyInterfaces"&gt;
			&lt;value&gt;com.zhaming.aop.restaurant.RestaurantService&lt;/value&gt;
		&lt;/property&gt;

		&lt;!-- 对这些方式做那些拦截:切面 --&gt;
		&lt;property name="interceptorNames"&gt;
			&lt;list&gt;
				&lt;!-- 
				&lt;value&gt;beforeAdvice&lt;/value&gt;
				 
				&lt;value&gt;afterAdvice&lt;/value&gt;
				--&gt;
				&lt;value&gt;compareAdvice&lt;/value&gt;
			&lt;/list&gt;
		&lt;/property&gt;

		&lt;property name="target"&gt;
			&lt;ref bean="restaurantServiceTarget" /&gt;
		&lt;/property&gt;
	&lt;/bean&gt;

&lt;/beans&gt;</pre>
 
<p> </p>
<p> </p>
<p><span style="color: #ff6600; font-size: small;"><span style="font-size: medium;">5.AOP能干什么？</span>
</span>
</p>
<p>现在回头看看最初问的五个问题，那些杂事，是不是可以对应到软件中的几个概念：日志记录，性能统计，安全控制，事务处理，异常处理，更衍生的提还有缓存，持久化，同步等</p>
              
              <br/><br/>
              <span style="color:red;">
                <a href="http://inter12.iteye.com/blog/1674047#comments" style="color:red;">已有 <strong>19</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
              </span>
              <br/><br/><br/>
<span style="color:#E28822;">ITeye推荐</span>
<br/>
<ul><li><a href='/clicks/433' target='_blank'><span style="color:red;font-weight:bold;">—软件人才免语言低担保 赴美带薪读研！— </span></a></li></ul>
<br/><br/><br/>
              <img src ="http://www.blogjava.net/inter12/aggbug/387838.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/inter12/" target="_blank">薛赵明</a> 2012-09-08 18:01 <a href="http://www.blogjava.net/inter12/archive/2012/09/08/387838.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>redis 学习笔记4--sortset</title><link>http://www.blogjava.net/inter12/archive/2012/07/26/387839.html</link><dc:creator>薛赵明</dc:creator><author>薛赵明</author><pubDate>Thu, 26 Jul 2012 07:01:00 GMT</pubDate><guid>http://www.blogjava.net/inter12/archive/2012/07/26/387839.html</guid><wfw:comment>http://www.blogjava.net/inter12/comments/387839.html</wfw:comment><comments>http://www.blogjava.net/inter12/archive/2012/07/26/387839.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/inter12/comments/commentRss/387839.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/inter12/services/trackbacks/387839.html</trackback:ping><description><![CDATA[
              <p>
</p>
<p>redis学习笔记3--sortSet</p>
<p>终于到最后一个数据结构了，加油！！</p>
<p>整体结构图：</p>
<p><a href="http://dl.iteye.com/upload/picture/pic/115995/0ee3789f-33e1-35ca-ac65-cbd6b4e4e147.jpg">http://dl.iteye.com/upload/picture/pic/115995/0ee3789f-33e1-35ca-ac65-cbd6b4e4e147.jpg</a></p>
<p> </p>
<p> </p>
<p>1.ZADD</p>
<p>语法： ZADD key score value </p>
<p>释义：添加执行分数的value， score必须是double类型的数值</p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; zadd z1 1 a </p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; zadd z1 2 b </p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; zadd z1 20 bb </p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; zadd z1 10 ff  </p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 </p>
<p>1) "a"</p>
<p>2) "b"</p>
<p>3) "ff"</p>
<p>4) "bb"</p>
<p> </p>
<p> </p>
<p>2.ZREM</p>
<p>语法:ZREM key value </p>
<p>释义：删除指定value的值</p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; ZREM z1 b            // 删除，指定value</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1         </p>
<p>1) "a"</p>
<p>2) "ff"</p>
<p>3) "bb"</p>
<p> </p>
<p> </p>
<p>3.ZCARD</p>
<p>语法：ZCARD key </p>
<p>释义：获取集合总数</p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 </p>
<p>1) "a"</p>
<p>2) "ff"</p>
<p>3) "bb"</p>
<p>redis 127.0.0.1:6379&gt; zcard z1 </p>
<p>(integer) 3</p>
<p> </p>
<p>4.ZCOUNT</p>
<p>语法:zcount key min max</p>
<p>释义：计算在指定范围内的元素数目</p>
<p>(1 6  ==== 1 &lt; x &lt;= 6        // 括号代表开区间</p>
<p>1 6   ==== 1 &lt;= x &lt;= 6</p>
<p> </p>
<p> </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 withscores </p>
<p>1) "a"</p>
<p>2) "1"</p>
<p>3) "ff"</p>
<p>4) "10"</p>
<p>5) "bb"</p>
<p>6) "20"</p>
<p>redis 127.0.0.1:6379&gt; zcount z1 5 10      // 闭区间，能取得10</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; zcount z1 5 (10     //开区间，无法得到10</p>
<p>(integer) 0</p>
<p> </p>
<p>5.ZSCORE</p>
<p>语法：ZSCORE key value</p>
<p>释义：获取指定key的分数</p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 withscores </p>
<p>1) "a"</p>
<p>2) "1"</p>
<p>3) "ff"</p>
<p>4) "10"</p>
<p>5) "bb"</p>
<p>6) "20"</p>
<p>redis 127.0.0.1:6379&gt; zscore z1 a             // 获取a的分数</p>
<p>"1"</p>
<p> </p>
<p>6.ZINCRBY</p>
<p>语法：ZINCRBY key score value </p>
<p>释义：对于指定的value进行加法操作</p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 withscores </p>
<p>1) "a"</p>
<p>2) "1"</p>
<p>3) "ff"</p>
<p>4) "10"</p>
<p>5) "bb"</p>
<p>6) "20"</p>
<p>redis 127.0.0.1:6379&gt; zincrby z1 10 a            // 对于a+10</p>
<p>"11"</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 withscores </p>
<p>1) "ff"</p>
<p>2) "10"</p>
<p>3) "a"</p>
<p>4) "11"</p>
<p>5) "bb"</p>
<p>6) "20"</p>
<p>redis 127.0.0.1:6379&gt; zincrby z1 -3 a          // 对于a-3</p>
<p>"8"</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 withscores </p>
<p>1) "a"</p>
<p>2) "8"</p>
<p>3) "ff"</p>
<p>4) "10"</p>
<p>5) "bb"</p>
<p>6) "20"</p>
<p> </p>
<p>7.ZRANGE|ZREVRANGE</p>
<p>语法：ZRANGE|ZREVRANGE key </p>
<p>释义：显示所有列表</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 </p>
<p>1) "a"</p>
<p>2) "ff"</p>
<p>3) "bb"</p>
<p>redis 127.0.0.1:6379&gt; zrevrange z1 0 -1 </p>
<p>1) "bb"</p>
<p>2) "ff"</p>
<p>3) "a"</p>
<p> </p>
<p>8.ZRANGEBYSCORE|ZREVRANGEBYSCORE</p>
<p>语法：ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]</p>
<p>释义：获取指定范围内的数值</p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 withscores </p>
<p>1) "a"</p>
<p>2) "8"</p>
<p>3) "ff"</p>
<p>4) "10"</p>
<p>5) "bb"</p>
<p>6) "20"</p>
<p>redis 127.0.0.1:6379&gt; zrangebyscore z1 -inf +inf   // 在不清楚最大最小范围的时间，可以采用这个 -inf +inf </p>
<p>1) "a"</p>
<p>2) "ff"</p>
<p>3) "bb"</p>
<p>redis 127.0.0.1:6379&gt; zrangebyscore z1 8 10       // 闭区间</p>
<p>1) "a"</p>
<p>2) "ff"</p>
<p>redis 127.0.0.1:6379&gt; zrangebyscore z1 (8 10      // 开区间</p>
<p>1) "ff"</p>
<p> </p>
<p> </p>
<p>9.ZRANK|ZREVRANK</p>
<p>语法：zrank|zremrank  key member</p>
<p>释义：获取指定值在集合中的排名，以０代表第一位　。（顺序或是逆序）</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 withscores </p>
<p>1) "a"</p>
<p>2) "8"</p>
<p>3) "ff"</p>
<p>4) "10"</p>
<p>5) "bb"</p>
<p>6) "20"</p>
<p>redis 127.0.0.1:6379&gt; zrank z1 a </p>
<p>(integer) 0</p>
<p>redis 127.0.0.1:6379&gt; zrank z1 ff　　　　　//顺序位置</p>
<p>(integer) 1 </p>
<p>redis 127.0.0.1:6379&gt; zrevrank z1 a        //逆序位置</p>
<p>(integer) 2</p>
<p> </p>
<p>10.ZREMRANGEBYRANK</p>
<p>语法 :ZREMRANGEBYRANK key min max </p>
<p>释义：删除指定下标的数据</p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 </p>
<p>1) "a"</p>
<p>2) "ff"</p>
<p>3) "bb"</p>
<p>redis 127.0.0.1:6379&gt; zremrangebyrank z1 0 1 </p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 </p>
<p>1) "bb"</p>
<p> </p>
<p>11.ZREMRANGEBYSCORE</p>
<p>语法 :ZREMRANGEBYSCORE key min max</p>
<p>释义：根据指定分数删除数据</p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 withscores </p>
<p>1) "bb"</p>
<p>2) "20"</p>
<p>redis 127.0.0.1:6379&gt; ZREMRANGEBYSCORE z1 -inf +inf    // 删除所有的数据 等同于  del z1</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; zrange z1 0 -1 </p>
<p>(empty list or set)</p>
<p> </p>
<p>12.ZINTERSTORE</p>
<p>语法：ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]</p>
<p>释义：</p>
<p>  计算给定的一个或多个有序集的交集，其中给定 key 的数量必须以 numkeys 参数指定，并将该交集(结果集)储存到 destination 。</p>
<p>  默认情况下，结果集中某个成员的 score 值是所有给定集下该成员 score 值之和.</p>
<p> </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; zrange s1 0 -1 </p>
<p>1) "a"</p>
<p>2) "b"</p>
<p>3) "c"</p>
<p>redis 127.0.0.1:6379&gt; zrange s2 0 -1 </p>
<p>1) "a"</p>
<p>2) "c"</p>
<p>3) "d"</p>
<p>redis 127.0.0.1:6379&gt; zinterstore s3 2 s1 s2 </p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; zrange s3 0 -1 </p>
<p>1) "a"</p>
<p>2) "c"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p> </p>
<p>13.ZUNIONSTORE</p>
<p>语法：ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]</p>
<p>释义：计算给定的一个或多个有序集的并集，其中给定 key 的数量必须以 numkeys 参数指定，并将该并集(结果集)储存到 destination 。</p>
<p>默认情况下，结果集中某个成员的 score 值是所有给定集下该成员 score 值之 和 。</p>
<p> </p>
<p>WEIGHTS</p>
<p> 使用 WEIGHTS 选项，你可以为 每个 给定有序集 分别 指定一个乘法因子(multiplication factor)，每个给定有序集的所有成员的 score 值在传递给聚合函数(aggregation function)之前都要先乘以该有序集的因子。</p>
<p> 如果没有指定 WEIGHTS 选项，乘法因子默认设置为 1 。</p>
<p> </p>
<p>AGGREGATE</p>
<p> 使用 AGGREGATE 选项，你可以指定并集的结果集的聚合方式。</p>
<p> 默认使用的参数 SUM ，可以将所有集合中某个成员的 score 值之 和 作为结果集中该成员的 score 值；使用参数 MIN ，可以将所有集合中某个成员的 最小 score 值作为结果集中该成员的 score 值；而参数 MAX 则是将所有集合中某个成员的 最大 score 值作为结果集中该成员的 score 值。</p>
<p> </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; zunionstore s3 2 s1 s2 </p>
<p>(integer) 4</p>
<p>redis 127.0.0.1:6379&gt; zrange s3 0 -1 </p>
<p>1) "b"</p>
<p>2) "a"</p>
<p>3) "c"</p>
<p>4) "d"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<div><br></div>

              
              <br/><br/>
              <span style="color:red;">
                <a href="http://inter12.iteye.com/blog/1607952#comments" style="color:red;">已有 <strong>0</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
              </span>
              <br/><br/><br/>
<span style="color:#E28822;">ITeye推荐</span>
<br/>
<ul><li><a href='/clicks/433' target='_blank'><span style="color:red;font-weight:bold;">—软件人才免语言低担保 赴美带薪读研！— </span></a></li></ul>
<br/><br/><br/>
              <img src ="http://www.blogjava.net/inter12/aggbug/387839.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/inter12/" target="_blank">薛赵明</a> 2012-07-26 15:01 <a href="http://www.blogjava.net/inter12/archive/2012/07/26/387839.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>redis 学习笔记3--set</title><link>http://www.blogjava.net/inter12/archive/2012/07/25/387840.html</link><dc:creator>薛赵明</dc:creator><author>薛赵明</author><pubDate>Wed, 25 Jul 2012 10:36:00 GMT</pubDate><guid>http://www.blogjava.net/inter12/archive/2012/07/25/387840.html</guid><wfw:comment>http://www.blogjava.net/inter12/comments/387840.html</wfw:comment><comments>http://www.blogjava.net/inter12/archive/2012/07/25/387840.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/inter12/comments/commentRss/387840.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/inter12/services/trackbacks/387840.html</trackback:ping><description><![CDATA[
              <p> </p>
<p>redis学习笔记3--set</p>
<p> </p>
<p>list:有序且，允许重复数据的链表 存在POP PUSH的概念</p>
<p>set: 无需序，不能重复的集合 主要是ADD </p>
<p>sortSet：有序，不能重复的集合 </p>
<p> </p>
<p>整体结构图：</p>
<p>http://dl.iteye.com/upload/picture/pic/115943/f6e6971a-0216-3fe3-b89c-4ec5b53b762a.jpg</p>
<p> </p>
<p> </p>
<p>1.SADD 新增元素</p>
<p>语法： sadd key value </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; sadd s1 1</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; sadd s1 2 </p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; sadd s2 3 </p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; sadd s1 1      // 重复数据不会被添加</p>
<p>(integer) 0</p>
<p>redis 127.0.0.1:6379&gt;</p>
<p> </p>
<p>2.SREM 删除元素</p>
<p>语法：srem key value </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; smembers s1 </p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>redis 127.0.0.1:6379&gt; srem s1 1       // 删除s1中的1元素</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; smembers s1 </p>
<p>1) "2"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p> </p>
<p>3.SMEMBERS 列出所有信息 类似list的 lrange</p>
<p>语法：smembers key </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; smembers s1 </p>
<p>1) "2"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p> </p>
<p> </p>
<p>4.SISMEMBER 判断是否存在该元素</p>
<p>语法：sismember key value </p>
<p>1:存在该元素</p>
<p>0:不存在</p>
<p> </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; sismember s1 1   存在的元素返回1</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; sismember s1 3   不存在的元素返回0</p>
<p>(integer) 0</p>
<p>redis 127.0.0.1:6379&gt;</p>
<p> </p>
<p>5.SCARD 计算集合中元素总数 size</p>
<p>语法：scard key </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; scard s1     // 返回集合中元素个数</p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p> </p>
<p>6.SMOVE 将集合中一个元素转移到另一个集合中</p>
<p>语法: smove  source destination value </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; smembers s1 </p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>redis 127.0.0.1:6379&gt; smembers s2</p>
<p>(empty list or set)</p>
<p>redis 127.0.0.1:6379&gt; smove s1 s2 1    // 将S1中的1移动到了S2中</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; smembers s1 </p>
<p>1) "2"</p>
<p>redis 127.0.0.1:6379&gt; smembers s2</p>
<p>1) "1"</p>
<p>redis 127.0.0.1:6379&gt;</p>
<p> </p>
<p>7.SPOP 随机弹出一个元素</p>
<p>语法： spop key </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; smembers s1 </p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>3) "3"</p>
<p>4) "4"</p>
<p>redis 127.0.0.1:6379&gt; spop s1           // 随机弹出一个元素</p>
<p>"3"</p>
<p>redis 127.0.0.1:6379&gt; smembers s1 </p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>3) "4"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p> </p>
<p>8.SRANDMEMBER 随机获取一个元素，但是不弹出集合,这个是跟SPOP唯一的区别</p>
<p>语法: SRANDMEMBER</p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; smembers s1 </p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>3) "4"</p>
<p>redis 127.0.0.1:6379&gt; SRANDMEMBER s1   //  随机取得一个数据，但是元素不会丢失</p>
<p>"4"</p>
<p>redis 127.0.0.1:6379&gt; smembers s1 </p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>3) "4"</p>
<p>redis 127.0.0.1:6379&gt;</p>
<p> </p>
<p>9.SINTER 取两个集合的交集</p>
<p>语法：SINTER key1 key2 </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; smembers s2 </p>
<p>1) "1"</p>
<p>2) "10"</p>
<p>redis 127.0.0.1:6379&gt; smembers s1 </p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>3) "4"</p>
<p>redis 127.0.0.1:6379&gt; sinter s1 s2    // 两个集合共同的元素是 1 </p>
<p>1) "1"</p>
<p>redis 127.0.0.1:6379&gt;</p>
<p> </p>
<p>10.SINTERSTORE 取两个集合的交集并保存到另一个集合中 </p>
<p>语法：SINTERSTORE destination key1 key2 </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; smembers s1</p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>3) "3"</p>
<p>redis 127.0.0.1:6379&gt; smembers s2</p>
<p>1) "1"</p>
<p>2) "10"</p>
<p>redis 127.0.0.1:6379&gt; smembers s3                 //这个时候S3是空的</p>
<p>(empty list or set)</p>
<p>redis 127.0.0.1:6379&gt; sinterstore s3  s1 s2       //取两个的交集并保存到s3 中</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; smembers s3</p>
<p>1) "1"</p>
<p>redis 127.0.0.1:6379&gt;</p>
<p> </p>
<p>11.SUNION  取两个集合的并集 </p>
<p>语法：SUNION key1 key2 </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; smembers s1</p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>3) "3"</p>
<p>redis 127.0.0.1:6379&gt; smembers s2</p>
<p>1) "1"</p>
<p>2) "10"</p>
<p>redis 127.0.0.1:6379&gt; sunion s1 s2              // 取得两个集合的并集</p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>3) "3"</p>
<p>4) "10"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p> </p>
<p>12.SUNIONSTORE 取两个集合的并集并保存到另一个集合中 </p>
<p>语法: SUNIONSTORE destination key1 key2  </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; smembers s1</p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>3) "3"</p>
<p>redis 127.0.0.1:6379&gt; smembers s2</p>
<p>1) "1"</p>
<p>2) "10"</p>
<p>redis 127.0.0.1:6379&gt; sunionstore  s4 s1 s2 </p>
<p>(integer) 4</p>
<p>redis 127.0.0.1:6379&gt; smembers s4</p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>3) "3"</p>
<p>4) "10"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p> </p>
<p>13.SDIFF 取两个集合的差集</p>
<p>语法：SDIFF key1 key2</p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; smembers s1</p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>3) "3"</p>
<p>redis 127.0.0.1:6379&gt; smembers s2</p>
<p>1) "1"</p>
<p>2) "10"</p>
<p>redis 127.0.0.1:6379&gt; sdiff s1 s2    // 获取的是两个之间的差集</p>
<p>1) "2"</p>
<p>2) "3"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p> </p>
<p>14.SDIFFSTORE 取两个集合的差集 并保存到第三个集合中</p>
<p>语法:SDIFFSTORE key1 key2 diffSet</p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; smembers s1</p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>3) "3"</p>
<p>redis 127.0.0.1:6379&gt; smembers s2</p>
<p>1) "1"</p>
<p>2) "10"</p>
<p>redis 127.0.0.1:6379&gt; sdiffstore s5  s1 s2    // 将差集的数据保存到s5中 </p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; smembers s5</p>
<p>1) "2"</p>
<p>2) "3"</p>
<div><br></div>
              
              <br/><br/>
              <span style="color:red;">
                <a href="http://inter12.iteye.com/blog/1606866#comments" style="color:red;">已有 <strong>0</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
              </span>
              <br/><br/><br/>
<span style="color:#E28822;">ITeye推荐</span>
<br/>
<ul><li><a href='/clicks/433' target='_blank'><span style="color:red;font-weight:bold;">—软件人才免语言低担保 赴美带薪读研！— </span></a></li></ul>
<br/><br/><br/>
              <img src ="http://www.blogjava.net/inter12/aggbug/387840.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/inter12/" target="_blank">薛赵明</a> 2012-07-25 18:36 <a href="http://www.blogjava.net/inter12/archive/2012/07/25/387840.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>redis 学习笔记2--List</title><link>http://www.blogjava.net/inter12/archive/2012/07/25/387841.html</link><dc:creator>薛赵明</dc:creator><author>薛赵明</author><pubDate>Wed, 25 Jul 2012 08:15:00 GMT</pubDate><guid>http://www.blogjava.net/inter12/archive/2012/07/25/387841.html</guid><wfw:comment>http://www.blogjava.net/inter12/comments/387841.html</wfw:comment><comments>http://www.blogjava.net/inter12/archive/2012/07/25/387841.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/inter12/comments/commentRss/387841.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/inter12/services/trackbacks/387841.html</trackback:ping><description><![CDATA[
              <p>LIST 整体结构图</p>
<p> </p>
<p>图画的太大了，只能放地址：</p>
<p><span style="">http://dl.iteye.com/upload/picture/pic/115935/8e96f42d-3a7b-3cea-85ae-997496aa9521.jpg</span></p>
<p> </p>
<p>LIST列表的操作，可想而知，对于列表我们需要的具备的功能列表</p>
<p>加入列表：</p>
<p>  从头部加入   LPUSH </p>
<p>  从底部加入   RPUSH</p>
<p>弹出列表</p>
<p>  从头部弹出   LPOP</p>
<p>  从底部弹出   RPOP</p>
<p>截取子列表     LRANGE</p>
<p>计算列表的长度 LLEN</p>
<p> </p>
<p> </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; rpush l2 1     // 从底部添加一个元素</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; rpush l2 2 </p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; lrange l2 0 -1 // 展示所有的元素  -1 代表所有</p>
<p>1) "1"</p>
<p>2) "2"</p>
<p>redis 127.0.0.1:6379&gt; lpush l2 3 </p>
<p>(integer) 3</p>
<p>redis 127.0.0.1:6379&gt; lrange l2 0 -1 </p>
<p>1) "3"</p>
<p>2) "1"</p>
<p>3) "2"</p>
<p>redis 127.0.0.1:6379&gt; lpop l2       // 从头部弹出一个元素</p>
<p>"3"</p>
<p>redis 127.0.0.1:6379&gt; rpop l2       // 从尾部弹出一个元素</p>
<p>"2"</p>
<p>redis 127.0.0.1:6379&gt; llen l2       // 计算队列的长度</p>
<p>(integer) 1</p>
<p> </p>
<p>扩展</p>
<p>1.1 LPUSHX  当且队列存在的情况下 在头部插入数据 </p>
<p> </p>
<p>LPUSHX key value</p>
<p> </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; LLEN empty1            </p>
<p>(integer) 0</p>
<p>redis 127.0.0.1:6379&gt; lpushx empty1 hh    // 将数据推入一个空的队列，结果失败</p>
<p>(integer) 0</p>
<p>redis 127.0.0.1:6379&gt; lpush l3 hh </p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; lpushx l3 hh2      // 将数据推入一个存在的队列头部，成功</p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; lrange l3 0 -1 </p>
<p>1) "hh2"</p>
<p>2) "hh"</p>
<p> </p>
<p>1.2 RPUSHX 当且队列存在的情况下 在尾部插入数据  基本同LPUSHX</p>
<p>redis 127.0.0.1:6379&gt; rpushx empty1 hh3    // 尝试将数据推入不存在队列的尾部，失败。</p>
<p>(integer) 0</p>
<p>redis 127.0.0.1:6379&gt; rpushx l3 hh3        // 将数据推入存在队列的尾巴，成功。</p>
<p>(integer) 3</p>
<p>redis 127.0.0.1:6379&gt; lrange l3 0 -1</p>
<p>1) "hh2"</p>
<p>2) "hh"</p>
<p>3) "hh3"</p>
<p> </p>
<p>1.3 BLPOP 对于LPOP的扩展 ，阻塞式的获取数据</p>
<p>BLPOP key [key ...] timeout</p>
<p> </p>
<p>它是 LPOP 命令的阻塞版本，当给定列表内没有任何元素可供弹出的时候，连接将被 BLPOP 命令阻塞，直到等待超时或发现可弹出元素为止。</p>
<p>当给定多个 key 参数时，按参数 key 的先后顺序依次检查各个列表，弹出第一个非空列表的头元素。</p>
<p> </p>
<p>假设现在有三个队列 job 为空 ， command  request 不为空 。那么就开始轮训所有的key ，若是空，则跳过 。执行顺序为 job ==&gt; command ==&gt; request</p>
<p> </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; del job </p>
<p>(integer) 0</p>
<p>redis 127.0.0.1:6379&gt; lpush command hh </p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; lpush request kk </p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; blpop job command kk </p>
<p>(error) ERR timeout is not an integer or out of range</p>
<p>redis 127.0.0.1:6379&gt; blpop job command kk  100 </p>
<p>1) "command"</p>
<p>2) "hh"</p>
<p>redis 127.0.0.1:6379&gt;</p>
<p> </p>
<p>查看其是如何阻塞的</p>
<p>在第一个图片中，上面那个终端一直阻塞着。</p>
<p><img height="90" alt="" width="120" src="http://dl.iteye.com/upload/picture/pic/115927/14881db1-d54d-3bc8-ada7-3482a381ce9a-thumb.jpg"></p>
<p> </p>
<p>在第二个终端中输入数据后，上面终端取得数据并返回。
</p>
<p><img height="90" alt="" width="120" src="http://dl.iteye.com/upload/picture/pic/115933/3f914783-b9d2-3a14-9aa4-8944353cf4ca-thumb.jpg"></p>
<p> </p>
<p>1.4 BRPOP 对于rpop的扩展，原理基本同BLPOP</p>
<p> </p>
<p>2.LREM</p>
<p>语法：LREM key count value</p>
<p>释义：根据参数 count 的值，移除列表中与参数 value 相等的元素。</p>
<p>count 的值可以是以下几种：</p>
<p>  count &gt; 0 : 从表头开始向表尾搜索，移除与 value 相等的元素，数量为 count 。</p>
<p>  count &lt; 0 : 从表尾开始向表头搜索，移除与 value 相等的元素，数量为 count 的绝对值。</p>
<p>  count = 0 : 移除表中所有与 value 相等的值。</p>
<p> </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; lpush l1 hello </p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; lpush l1 world </p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; lpush l1 hello </p>
<p>(integer) 3</p>
<p>redis 127.0.0.1:6379&gt; lpush l1 world </p>
<p>(integer) 4</p>
<p>redis 127.0.0.1:6379&gt; lpush l1 hello </p>
<p>(integer) 5</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1 </p>
<p>1) "hello"</p>
<p>2) "world"</p>
<p>3) "hello"</p>
<p>4) "world"</p>
<p>5) "hello"</p>
<p>redis 127.0.0.1:6379&gt;                       // 数据准备完成</p>
<p> </p>
<p>redis 127.0.0.1:6379&gt; lrem l1 2 hello      // 从头部开始扫描，移除了两个hello </p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; lrem l1 -1 hello     // 从尾部开始扫描，移除了一个hello</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1       // 现在只剩下 world了</p>
<p>1) "world"</p>
<p>2) "world"</p>
<p>redis 127.0.0.1:6379&gt; lrem l1 0 world     // 移除队中所有的world </p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1      // 已经被清空了。</p>
<p>(empty list or set)</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p> </p>
<p>3.LSET</p>
<p>语法：LSET key index value</p>
<p>释义：将列表 key 下标为 index 的元素的值设置为 value 。</p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; lpush l1 hello </p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; lpush l1 world </p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; lset l1 1  ---      // 下标为1的数据被替换成 --- 了</p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1</p>
<p>1) "world"</p>
<p>2) "---"</p>
<p>redis 127.0.0.1:6379&gt; lset l1 3 hh       // 超出下标进行设置的话，报错</p>
<p>(error) ERR index out of range</p>
<p>redis 127.0.0.1:6379&gt; exist l2 </p>
<p>(error) ERR unknown command 'exist'</p>
<p>redis 127.0.0.1:6379&gt; exists l2          // 对不存在的队列进行设置的话，报错</p>
<p>(integer) 0</p>
<p>redis 127.0.0.1:6379&gt; lset l2 0 hh </p>
<p>(error) ERR no such key</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p> </p>
<p> </p>
<p>4 LTRIM</p>
<p>语法：LTRIM key start stop</p>
<p>释义：对一个列表进行修剪(trim)，就是说，让列表只保留指定区间内的元素，不在指定区间之内的元素都将被删除。</p>
<p> 超出范围的下标值不会引起错误。</p>
<p> 如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大，或者 start &gt; stop ， LTRIM 返回一个空列表(因为 LTRIM 已经将整个列表清空)。</p>
<p> 如果 stop 下标比 end 下标还要大，Redis将 stop 的值设置为 end </p>
<p> </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1    // 新建一个队列 </p>
<p>1) "h"</p>
<p>2) "e"</p>
<p>3) "l"</p>
<p>4) "l"</p>
<p>5) "o"</p>
<p>redis 127.0.0.1:6379&gt; ltrim l1 0 3     // 只截取前四个 </p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1</p>
<p>1) "h"</p>
<p>2) "e"</p>
<p>3) "l"</p>
<p>4) "l"</p>
<p>redis 127.0.0.1:6379&gt; ltrim l1 0 10    // stop下标大于队列长度 则 stop=队列长度</p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1</p>
<p>1) "h"</p>
<p>2) "e"</p>
<p>3) "l"</p>
<p>4) "l"</p>
<p>redis 127.0.0.1:6379&gt; lset l1 10 20  // start stop 都大于 队列长度 且 start &lt;　stop 清空队列</p>
<p>(empty list or set)</p>
<p>redis 127.0.0.1:6379&gt; ltrim l1 3 1     // start  &lt;　stop 清空队列</p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 10 20 </p>
<p>(empty list or set)</p>
<p> </p>
<p>5.LINDEX</p>
<p>语法：LINDEX key index</p>
<p>释义：返回列表 key 中，下标为 index 的元素。</p>
<p> 下标(index)参数 start 和 stop 都以 0 为底，也就是说，以 0 表示列表的第一个元素，以 1 表示列表的第二个元素，以此类推。</p>
<p> 你也可以使用负数下标，以 -1 表示列表的最后一个元素， -2 表示列表的倒数第二个元素，以此类推。</p>
<p> </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; lpush l1 1 </p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; lpush l1 2</p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; lpush l1 3</p>
<p>(integer) 3</p>
<p>redis 127.0.0.1:6379&gt; lindex l1 0            // 取下标为0的数据</p>
<p>"3"</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1 </p>
<p>1) "3"</p>
<p>2) "2"</p>
<p>3) "1"</p>
<p>redis 127.0.0.1:6379&gt; lindex l1 -1          // 取最后一个数据</p>
<p>"1"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p> </p>
<p>6. LINSERT  类似于LSET,一个是根据下标来插入，一个是根据pivot来插入数据。</p>
<p>语法：LINSERT key BEFORE|AFTER pivot value</p>
<p>释义：将值 value 插入到列表 key 当中，位于值 pivot 之前或之后。</p>
<p>  当 pivot 不存在于列表 key 时，不执行任何操作。</p>
<p>  当 key 不存在时， key 被视为空列表，不执行任何操作。</p>
<p>  如果 key 不是列表类型，返回一个错误。</p>
<p> </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1 </p>
<p>1) "redis"</p>
<p>2) "hello"</p>
<p>3) "world"</p>
<p>4) "hello"</p>
<p>redis 127.0.0.1:6379&gt; linsert l1 after hello after-insert          // 在第一个找到的hello后面插入了一个数据 </p>
<p>(integer) 5</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1 </p>
<p>1) "redis"</p>
<p>2) "hello"</p>
<p>3) "after-insert"</p>
<p>4) "world"</p>
<p>5) "hello"</p>
<p>redis 127.0.0.1:6379&gt; linsert l1 before hello before-insert       // 在第一个找到的hello前面插入了一个数据 </p>
<p>(integer) 6</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1 </p>
<p>1) "redis"</p>
<p>2) "before-insert"</p>
<p>3) "hello"</p>
<p>4) "after-insert"</p>
<p>5) "world"</p>
<p>6) "hello"</p>
<p>redis 127.0.0.1:6379&gt; linsert l1 before hoho before-insert       // 对于 pivot 不存在的列表，插入失败</p>
<p>(integer) -1</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1 </p>
<p>1) "redis"</p>
<p>2) "before-insert"</p>
<p>3) "hello"</p>
<p>4) "after-insert"</p>
<p>5) "world"</p>
<p>6) "hello"</p>
<p>redis 127.0.0.1:6379&gt; linsert l2 before hoho before-insert       // 插入一个空列表，直接报错</p>
<p>(integer) 0</p>
<p> </p>
<p>7. RPOPLPUSH </p>
<p>语法：RPOPLPUSH source destination</p>
<p>释义：</p>
<p>命令 RPOPLPUSH 在一个原子时间内，执行以下两个动作：</p>
<p> 将列表 source 中的最后一个元素(尾元素)弹出，并返回给客户端。</p>
<p> 将 source 弹出的元素插入到列表 destination ，作为 destination 列表的的头元素。</p>
<p>举个例子，你有两个列表 source 和 destination ， source 列表有元素 a, b, c ， destination 列表有元素 x, y, z ，执行 RPOPLPUSH source destination 之后， source 列表包含元素 a, b ， destination 列表包含元素 c, x, y, z ，并且元素 c 会被返回给客户端。</p>
<p> 如果 source 不存在，值 nil 被返回，并且不执行其他动作。</p>
<p> 如果 source 和 destination 相同，则列表中的表尾元素被移动到表头，并返回该元素，可以把这种特殊情况视作列表的旋转(rotation)操作。</p>
<p> </p>
<p>实践：</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1 </p>
<p>1) "c"</p>
<p>2) "b"</p>
<p>3) "a"</p>
<p>redis 127.0.0.1:6379&gt; lrange l2 0 -1</p>
<p>1) "3"</p>
<p>2) "2"</p>
<p>3) "1"</p>
<p>redis 127.0.0.1:6379&gt; rpoplpush l1 l2     //将l1尾部的数据弹出进入l2的头部,并将这个弹出的数据返回</p>
<p>"a"</p>
<p>redis 127.0.0.1:6379&gt; lrange l1 0 -1 </p>
<p>1) "c"</p>
<p>2) "b"</p>
<p>redis 127.0.0.1:6379&gt; lrange l2 0 -1 </p>
<p>1) "a"</p>
<p>2) "3"</p>
<p>3) "2"</p>
<p>4) "1"</p>
<p>redis 127.0.0.1:6379&gt;</p>
<p> </p>
<p>8. BRPOPLPUSH </p>
<p>语法：BRPOPLPUSH source destination timeout</p>
<p>释义：BRPOPLPUSH 是 RPOPLPUSH 的阻塞版本，当给定列表 source 不为空时， BRPOPLPUSH 的表现和 RPOPLPUSH 一样。</p>
<p>   当列表 source 为空时， BRPOPLPUSH 命令将阻塞连接，直到等待超时，或有另一个客户端对 source 执行 LPUSH 或 RPUSH 命令为止。</p>
<p>   超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。</p>
<p> </p>
<p>实践：</p>
<p>我们设置等超时时间为1000 </p>
<p><img height="90" alt="" width="120" src="http://dl.iteye.com/upload/picture/pic/115931/763e18a6-c09b-3228-854e-51f5f7758c61-thumb.jpg"></p>
<p> </p>
<p>在另一个客户端添加数据后，就转移了数据</p>
<p><img height="90" alt="" width="120" src="http://dl.iteye.com/upload/picture/pic/115929/11e40dcf-1ba6-3a58-80c5-46debb77bef1-thumb.jpg"></p>
<p> </p>
<p> </p>
<p> </p>
              
              <br/><br/>
              <span style="color:red;">
                <a href="http://inter12.iteye.com/blog/1606604#comments" style="color:red;">已有 <strong>0</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
              </span>
              <br/><br/><br/>
<span style="color:#E28822;">ITeye推荐</span>
<br/>
<ul><li><a href='/clicks/433' target='_blank'><span style="color:red;font-weight:bold;">—软件人才免语言低担保 赴美带薪读研！— </span></a></li></ul>
<br/><br/><br/>
              <img src ="http://www.blogjava.net/inter12/aggbug/387841.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/inter12/" target="_blank">薛赵明</a> 2012-07-25 16:15 <a href="http://www.blogjava.net/inter12/archive/2012/07/25/387841.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>redis学习笔记1--string</title><link>http://www.blogjava.net/inter12/archive/2012/07/24/387842.html</link><dc:creator>薛赵明</dc:creator><author>薛赵明</author><pubDate>Tue, 24 Jul 2012 09:29:00 GMT</pubDate><guid>http://www.blogjava.net/inter12/archive/2012/07/24/387842.html</guid><wfw:comment>http://www.blogjava.net/inter12/comments/387842.html</wfw:comment><comments>http://www.blogjava.net/inter12/archive/2012/07/24/387842.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/inter12/comments/commentRss/387842.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/inter12/services/trackbacks/387842.html</trackback:ping><description><![CDATA[
              <p> </p>
<p>NOSQL的学习笔记：</p>
<p>1.最基本的命令</p>
<p>相信所有的NOSQL都会提供了命令：GET SET DEL </p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; set ee 10 </p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; get ee </p>
<p>"10"</p>
<p>redis 127.0.0.1:6379&gt; del ee              // 返回值 1:代表正确 0：代表错误</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; get ee  </p>
<p>(nil)</p>
<p>redis 127.0.0.1:6379&gt;</p>
<p>--------------------------------------</p>
<p> </p>
<p>del key1 key2 key3  //可删除多个key</p>
<p> </p>
<p> </p>
<p>扩展：</p>
<p>1.1 SETNX:设置一个值，如果不存在的话,已经存在则不新增.对于SET的扩展。</p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; set ee 10 </p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; setnx ee 20 </p>
<p>(integer) 0</p>
<p>redis 127.0.0.1:6379&gt; get ee             // 因为之前存在ee  所以ee的值并没有发生变化。</p>
<p>"10"</p>
<p>redis 127.0.0.1:6379&gt; setnx aa 20        //  aa 之前不存在，所以设置成功！</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; get aa </p>
<p>"20"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p>--------------------------------------</p>
<p> </p>
<p>1.2 SETEX 设置过期时间，对于SET的扩展。若是已经存在，会覆盖原来的值。</p>
<p>语法:</p>
<p>SETEX key seconds value</p>
<p> </p>
<p>要求版本：&gt;= 2.0.0</p>
<p> </p>
<p>实践：</p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; setex ee 10 20 </p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; get ee </p>
<p>"20"</p>
<p>redis 127.0.0.1:6379&gt; ttl ee </p>
<p>(integer) 4</p>
<p>redis 127.0.0.1:6379&gt; ttl ee </p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; ttl ee </p>
<p>(integer) -1</p>
<p>redis 127.0.0.1:6379&gt; get ee </p>
<p>(nil)</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p>--------------------------------------</p>
<p> </p>
<p>限制：</p>
<p>等同于一下语句，不过是一个原子性的操作，很适合做缓存使用。</p>
<p>SET key value</p>
<p>EXPIRE key seconds  # 设置生存时间</p>
<p> </p>
<p>1.3 PSETEX  对于 SETEX的再次扩展，唯一的区别是以毫秒为单位，不是以秒为单位</p>
<p>语法：</p>
<p>PSETEX key milliseconds value</p>
<p> </p>
<p>要求版本：&gt;= 2.6.0</p>
<p>实践：</p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; setex ee 10 20 </p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; get ee </p>
<p>"20"</p>
<p>redis 127.0.0.1:6379&gt; ttl ee </p>
<p>(integer) 4</p>
<p>redis 127.0.0.1:6379&gt; ttl ee </p>
<p>(integer) 2</p>
<p>redis 127.0.0.1:6379&gt; ttl ee </p>
<p>(integer) -1</p>
<p>redis 127.0.0.1:6379&gt; get ee </p>
<p>(nil)</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p>--------------------------------------</p>
<p> </p>
<p>1.4 MGET , MSET</p>
<p>获取多个值或是设置多个值。</p>
<p> </p>
<p>MSET ：替换旧值，原子操作。</p>
<p> </p>
<p>实践：</p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; mset key1 haha key2 hehe </p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; mget key1 key2 </p>
<p>1) "haha"</p>
<p>2) "hehe"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p>--------------------------------------</p>
<p> </p>
<p>1.5 MSETNX ：设置多个key value,仅当key存在时  </p>
<p>MSETNX key value [key value ...] </p>
<p>既然有setnx 就会有 这个命令</p>
<p> </p>
<p>实践：</p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; msetnx key1 hehe key3 hoho              //因为这是一个原子的操作，所以key1已经存在，所以整体失败了！</p>
<p>(integer) 0</p>
<p>redis 127.0.0.1:6379&gt; mget key1 key2 key3                     // 找不到所要的key3</p>
<p>1) "haha"</p>
<p>2) "hehe"</p>
<p>3) (nil)</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p>--------------------------------------</p>
<p> </p>
<p>1.6 GETSET</p>
<p>GETSET key value 设置一个key的value，并获取设置前的值 。相当于重置功能。</p>
<p> </p>
<p> </p>
<p>实践：</p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; incrby count 10 </p>
<p>(integer) 10</p>
<p>redis 127.0.0.1:6379&gt; getset count "5"    // 这个时候返回的是10，不是5</p>
<p>"10"</p>
<p>redis 127.0.0.1:6379&gt; get count </p>
<p>"5"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p>--------------------------------------</p>
<p>有时我们需要获取计数器的值，并且自动将其重置为0</p>
<p> </p>
<p>1.7 GETRANGE 获取存储在一个关键的一个子字符串</p>
<p> </p>
<p>实践：</p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; set longworld "hello world!"</p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; getrange  longworld 0 5 </p>
<p>"hello "</p>
<p>redis 127.0.0.1:6379&gt; getrange longworld -6 -1 </p>
<p>"world!"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p>redis 127.0.0.1:6379&gt; getrange longworld 5 100     // 超出范围的数据只取最后位</p>
<p>" world!"</p>
<p>--------------------------------------</p>
<p> </p>
<p>1.8 SETRANGE 类似于GETRANGE 覆盖在指定的偏移量开始的关键字符串的一部分</p>
<p> </p>
<p>实践：</p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; setrange longworld 6 "redis"</p>
<p>(integer) 12</p>
<p>redis 127.0.0.1:6379&gt; get longworld </p>
<p>"hello redis!"</p>
<p>redis 127.0.0.1:6379&gt; </p>
<p>--------------------------------------</p>
<p> </p>
<p>1.9 STRLEN  计算长度</p>
<p>语法：</p>
<p>STRLEN key </p>
<p> </p>
<p>实践：</p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; strlen longworld </p>
<p>(integer) 12</p>
<p>--------------------------------------</p>
<p> </p>
<p>1.10 append 追加数据 ，setrange是截取字符</p>
<p>语法：</p>
<p>append key str</p>
<p> </p>
<p>实践：</p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; append longworld "!!!"</p>
<p>(integer) 15</p>
<p>redis 127.0.0.1:6379&gt; get longworld </p>
<p>"hello redis!!!!"</p>
<p>--------------------------------------</p>
<p> </p>
<p> </p>
<p>3.INCR :对于一个数值做递增，步伐是1。</p>
<p>限制：</p>
<p>只允许对于数值类型做操作，若是字符串类型则报错。</p>
<p> </p>
<p>是否线程安全：是的，是一个原子操作，不用担心多线程并发修改同一个值得问题。</p>
<p>即不会出现一下情况：</p>
<p>--------------------------------------</p>
<p>    Client A reads count as 10.</p>
<p>    Client B reads count as 10.</p>
<p>    Client A increments 10 and sets count to 11.</p>
<p>    Client B increments 10 and sets count to 11.</p>
<p>--------------------------------------</p>
<p> </p>
<p>若是希望递增的频率不是1呢，那么使用INCRBY </p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; set ee 10 </p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; incrby ee 2     // 第三个参数是步频</p>
<p>(integer) 12</p>
<p>redis 127.0.0.1:6379&gt; get ee </p>
<p>"12"</p>
<p>--------------------------------------</p>
<p> </p>
<p>扩展：</p>
<p>INCR &amp;&amp;INCRBY 对应的命令是 DECR ，DECRBY</p>
<p> </p>
<p>4.如何设置一个key的过期时间呢？</p>
<p>简单，通过 EXPIRE来设置，通过TTL命令查看。</p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; set ee 10</p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; expire ee 10          // 设置过期时间为10秒</p>
<p>(integer) 1</p>
<p>redis 127.0.0.1:6379&gt; ttl ee</p>
<p>(integer) 8</p>
<p>redis 127.0.0.1:6379&gt; ttl ee</p>
<p>(integer) 3</p>
<p>redis 127.0.0.1:6379&gt; ttl ee                // 已经过期了。</p>
<p>(integer) -1</p>
<p>redis 127.0.0.1:6379&gt; get ee                // 已经取不到值了  </p>
<p>(nil)</p>
<p>redis 127.0.0.1:6379&gt;                  </p>
<p>--------------------------------------</p>
<p> </p>
<p>若是不设置expire ，只是set 一个值后，再通过ttl 查看 返回结果是 -1 ，代表永远不过期。</p>
<p>--------------------------------------</p>
<p>redis 127.0.0.1:6379&gt; set ee 10 </p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; ttl ee </p>
<p>(integer) -1</p>
<p>redis 127.0.0.1:6379&gt; get ee </p>
<p>"10"</p>
<p>--------------------------------------</p>
<p> </p>
<p>一个整体结构图：</p>
<p>图太大 还是自己贴地址吧：</p>
<p><a href="http://dl.iteye.com/upload/picture/pic/115893/840bfd7b-765e-3884-8253-0c7b3ec9db4c.jpg">http://dl.iteye.com/upload/picture/pic/115893/840bfd7b-765e-3884-8253-0c7b3ec9db4c.jpg</a></p>
<p> </p>
<p> </p>
              
              <br/><br/>
              <span style="color:red;">
                <a href="http://inter12.iteye.com/blog/1605790#comments" style="color:red;">已有 <strong>0</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
              </span>
              <br/><br/><br/>
<span style="color:#E28822;">ITeye推荐</span>
<br/>
<ul><li><a href='/clicks/433' target='_blank'><span style="color:red;font-weight:bold;">—软件人才免语言低担保 赴美带薪读研！— </span></a></li></ul>
<br/><br/><br/>
              <img src ="http://www.blogjava.net/inter12/aggbug/387842.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/inter12/" target="_blank">薛赵明</a> 2012-07-24 17:29 <a href="http://www.blogjava.net/inter12/archive/2012/07/24/387842.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>redis 安装笔记</title><link>http://www.blogjava.net/inter12/archive/2012/07/24/387843.html</link><dc:creator>薛赵明</dc:creator><author>薛赵明</author><pubDate>Tue, 24 Jul 2012 09:26:00 GMT</pubDate><guid>http://www.blogjava.net/inter12/archive/2012/07/24/387843.html</guid><wfw:comment>http://www.blogjava.net/inter12/comments/387843.html</wfw:comment><comments>http://www.blogjava.net/inter12/archive/2012/07/24/387843.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/inter12/comments/commentRss/387843.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/inter12/services/trackbacks/387843.html</trackback:ping><description><![CDATA[
              <p><strong>1.redis的安装</strong></p>
<p>wget http://download.redis.io/redis-stable.tar.gz</p>
<p>tar xvzf redis-stable.tar.gz</p>
<p>cd redis-stable</p>
<p>make</p>
<p>如此就已经安装完成了，当然也可以采用make test来看看安装是否正确。</p>
<p> </p>
<p>基本常用命令：</p>
<p>redis-server ：运行一个redisserver</p>
<p>redis-cli :redis命令行版本的客户端，同本地或是远程redis服务进行交互</p>
<p>redis-benchmark : 检查redis服务性能的命令</p>
<p>redis-check-aof and redis-check-dump are useful in the rare event of corrupted data files.</p>
<p> </p>
<p>若是在make的时候没有将redis-server，redis-cli拷贝到/usr/local/bin目录下，那么手工拷贝下。</p>
<p> </p>
<p>sudo cp redis-server /usr/local/bin/</p>
<p>sudo cp redis-cli /usr/local/bin/</p>
<p> </p>
<p><strong>2.启动redis</strong></p>
<p>inter12@inter12-VirtualBox:~$ redis-server</p>
<p>[2233] 27 Jun 09:23:04 # Warning: no config file specified, using the default config. In order to specify a config file use 'redis-server /path/to/redis.conf'</p>
<p>[2233] 27 Jun 09:23:04 * Server started, Redis version 2.4.15</p>
<p>[2233] 27 Jun 09:23:04 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.</p>
<p>[2233] 27 Jun 09:23:04 * DB loaded from disk: 0 seconds</p>
<p>[2233] 27 Jun 09:23:04 * The server is now ready to accept connections on port 6379</p>
<p>[2233] 27 Jun 09:23:04 - DB 0: 1 keys (0 volatile) in 4 slots HT.</p>
<p>[2233] 27 Jun 09:23:04 - 0 clients connected (0 slaves), 717624 bytes in use</p>
<p> </p>
<p>不带任何参数的情况，采用的是默认的配置文件。这个只适用于开发环境，生成环境需要自己制定一个配置文件。具体命令是： redis-server /etc/redis.conf </p>
<p> </p>
<p><strong>3.检查redis是否正常工作</strong></p>
<p>inter12@inter12-VirtualBox:~$ redis-cli ping</p>
<p>PONG</p>
<p> </p>
<p>返回PONG就代表是OK的。</p>
<p> </p>
<p><strong>4.redis的常用命令</strong></p>
<p>redis 127.0.0.1:6379&gt; set haha 'zhaoming'</p>
<p>OK</p>
<p>redis 127.0.0.1:6379&gt; get haha</p>
<p>"zhaoming"</p>
<p> </p>
<p>所有完整的命令可见:</p>
<p>http://redis.readthedocs.org/en/latest/</p>
<p> </p>
<p>还有所有可用客户端：</p>
<p>http://redis.io/clients</p>
<p> </p>
<p><strong>5.redis的持久化</strong></p>
<p>因为是内存型的NOSQL，有两种方式。1.使用save命令，会将数据刷新到文件中。2.采用redis-cli shutdown 会将内存中数据刷新到文件中</p>
<p>更详细的可见：</p>
<p>http://redis.io/topics/persistence</p>
<p> </p>
<p><strong>6.正确的部署redis</strong></p>
<p>1.采用界面部署</p>
<p>2.在linux上，采用一个初始化脚本部署(更推荐这种)</p>
<p> </p>
<p>如何采用第二种进行部署呢。</p>
<p>s1)建立配置文件及数据文件目录</p>
<p>mkdir /etc/redis </p>
<p>mkdir /var/redis </p>
<p> </p>
<p>s2)将初始化脚本放到/etc/init.d目录下,并建议根据端口号进行文件命名</p>
<p>cp /home/inter12/install/soft/redis/redis-stable/utils/redis_init_script /etc/init.d/redis_6379</p>
<p>编辑 /etc/init.d/redis_6379 ，确保端口是你想设置的。</p>
<p> </p>
<p>s3)拷贝redis.conf到 /etc/redis目录下</p>
<p>sudo cp /home/inter12/install/soft/redis/redis-stable/redis.conf /etc/redis/6379.conf</p>
<p> </p>
<p>s4)在/var/redis目录下建立一个redis实例对应的目录</p>
<p>mkdir /var/redis/6379 </p>
<p> </p>
<p>s5)修改配置文件</p>
<p>设置daemonize 为yes(默认是no)</p>
<p>设置pidfile 到 /var/run/redis_6379.pid(可以改变端口)</p>
<p>设置日志级别loglevel</p>
<p>设置logfile 到 /var/log/redis_6379.log</p>
<p>设置 dir 到 /var/redis/6379 (这个是最重要的)</p>
<p> </p>
<p>s6)最后添加初始化配置文件到所有运行级别下</p>
<p>sudo update-rc.d redis_6379 defaults   // 告诉系统启动时候，自动执行redis_6379这个脚本。</p>
<p> </p>
<p>如此就搞定了所有配置修改工作，就可以如此启动了：</p>
<p>/etc/init.d/redis_6379 start</p>
<p> </p>
<p>通过以下方式确保运行成功：</p>
<p>通过 redis-cli 尝试连接</p>
<p>运行 redis-cli ，然后 save 检查是否生成一个数据文件到 /var/redis/6379/目录下。 (应该能找到一个 dump.rdb文件).</p>
<p>检查是否在 /var/redis/6379/目录下生成文件</p>
<p>重启后，再次检查以上步骤。</p>
              
              <br/><br/>
              <span style="color:red;">
                <a href="http://inter12.iteye.com/blog/1605784#comments" style="color:red;">已有 <strong>0</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
              </span>
              <br/><br/><br/>
<span style="color:#E28822;">ITeye推荐</span>
<br/>
<ul><li><a href='/clicks/433' target='_blank'><span style="color:red;font-weight:bold;">—软件人才免语言低担保 赴美带薪读研！— </span></a></li></ul>
<br/><br/><br/>
              <img src ="http://www.blogjava.net/inter12/aggbug/387843.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/inter12/" target="_blank">薛赵明</a> 2012-07-24 17:26 <a href="http://www.blogjava.net/inter12/archive/2012/07/24/387843.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>AMQP--rabbitmq--1</title><link>http://www.blogjava.net/inter12/archive/2012/07/17/387844.html</link><dc:creator>薛赵明</dc:creator><author>薛赵明</author><pubDate>Tue, 17 Jul 2012 09:39:00 GMT</pubDate><guid>http://www.blogjava.net/inter12/archive/2012/07/17/387844.html</guid><wfw:comment>http://www.blogjava.net/inter12/comments/387844.html</wfw:comment><comments>http://www.blogjava.net/inter12/archive/2012/07/17/387844.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/inter12/comments/commentRss/387844.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/inter12/services/trackbacks/387844.html</trackback:ping><description><![CDATA[
              <p>1.基本安装</p>
<p>分为 server  + client </p>
<p> </p>
<p>server的安装：</p>
<p> </p>
<p>1.添加 deb http://www.rabbitmq.com/debian/ testing main 到 /etc/apt/sources.list</p>
<p> </p>
<p>2.apt-get update.</p>
<p> </p>
<p>3.sudo apt-get install rabbitmq-server</p>
<p>这个步骤会自动启动 rabbitmq-server 服务。</p>
<p> </p>
<p>常用命令：</p>
<p> </p>
<p>rabbitmqctl -h </p>
<p>rabbitmqctl status <span style="white-space: pre;">	</span></p>
<p>rabbitmqctl stop</p>
<p>rabbitmqctl start_app</p>
<p> </p>
<p> </p>
<p>客户端安装：</p>
<p>maven:</p>
<p>
</p>
<p>&lt;dependency&gt;</p>
<p>  &lt;groupId&gt;com.rabbitmq&lt;/groupId&gt;</p>
<p>  &lt;artifactId&gt;amqp-client&lt;/artifactId&gt;</p>
<p>  &lt;version&gt;2.8.4&lt;/version&gt;</p>
<p>&lt;/dependency&gt;</p>

<p> </p>
<p>或是下载链接：</p>
<p>wget http://www.rabbitmq.com/releases/rabbitmq-java-client/v2.8.4/rabbitmq-java-client-bin-2.8.4.tar.gz</p>
<p> </p>
<p>客户端编码---发送者：</p>
<p> </p>
<p>
</p>
<pre class="java" name="code">package com.jieting.mq.rabbit.send;

import java.io.IOException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class MessageSend {

    private static final String QUENE_NAME = "hello";

    public static void main(String[] args) throws IOException {

        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");

        Connection newConnection = connectionFactory.newConnection();
        Channel createChannel = newConnection.createChannel();

        createChannel.queueDeclare(QUENE_NAME, true, false, false, null);
        String message = "hello rabbitmq world!";
        createChannel.basicPublish("", QUENE_NAME, null, message.getBytes());

        System.out.println(" [x] Sent '" + message + "'");

        createChannel.close();
        newConnection.close();

    }
}
</pre>

<p> </p>
<p>消费者代码：</p>
<p> </p>
<p>
</p>
<pre class="java" name="code">package com.jieting.mq.rabbit.receive;

import java.io.IOException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException;

public class MessageReceive {

    private static final String QUENE_NAME = "hello";

    public static void main(String[] args) throws IOException, ShutdownSignalException, ConsumerCancelledException,
                                          InterruptedException {

        ConnectionFactory connectionFactory = new ConnectionFactory();
        Connection newConnection = connectionFactory.newConnection();
        Channel createChannel = newConnection.createChannel();

        createChannel.queueDeclare(QUENE_NAME, true, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        QueueingConsumer queueingConsumer = new QueueingConsumer(createChannel);
        createChannel.basicConsume(QUENE_NAME, true, queueingConsumer);
        while (true) {
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received '" + message + "'");
        }
    }
}
</pre>

<p> </p>
<p>以上资料都可从 一下地址找到：</p>
<p>http://www.rabbitmq.com/java-client.html</p>
<p>http://www.rabbitmq.com/getstarted.html</p>
<p> </p>
              
              <br/><br/>
              <span style="color:red;">
                <a href="http://inter12.iteye.com/blog/1596634#comments" style="color:red;">已有 <strong>0</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
              </span>
              <br/><br/><br/>
<span style="color:#E28822;">ITeye推荐</span>
<br/>
<ul><li><a href='/clicks/433' target='_blank'><span style="color:red;font-weight:bold;">—软件人才免语言低担保 赴美带薪读研！— </span></a></li></ul>
<br/><br/><br/>
              <img src ="http://www.blogjava.net/inter12/aggbug/387844.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/inter12/" target="_blank">薛赵明</a> 2012-07-17 17:39 <a href="http://www.blogjava.net/inter12/archive/2012/07/17/387844.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>