<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>SilenT&#39;s Blog</title>
    <link>http://Piscesilent.github.io/</link>
    <description>Recent content on SilenT&#39;s Blog</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-CN</language>
    <lastBuildDate>Thu, 23 Mar 2017 00:50:21 +0800</lastBuildDate>
    <atom:link href="http://Piscesilent.github.io/feed/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>记一次Young GC引发的线上问题</title>
      <link>http://piscesilent.github.io/post/young_gc_online_problem/</link>
      <pubDate>Thu, 23 Mar 2017 00:50:21 +0800</pubDate>
      
      <guid>http://piscesilent.github.io/post/young_gc_online_problem/</guid>
      <description>

&lt;p&gt;最近将项目迁移到springboot上，在线上遇到一个不大不小的问题。表象上是每台服务器的程序处理了30到40个请求之后，下一个请求的响应时间必定是在1秒以上。&lt;/p&gt;

&lt;h2 id=&#34;一-线上寻找问题&#34;&gt;一、线上寻找问题&lt;/h2&gt;

&lt;p&gt;当发现问题后，第一反应是怀疑瓶颈在io上，即数据库的问题。但是之前对线上的数据库进行过基准测试和压力测试，根据问题发生的情况和现场简单测试，立马就排除了数据库的问题，因为简单的OPTIONS请求也可以引发这个问题。&lt;/p&gt;

&lt;p&gt;这时候只能登陆服务器排查问题，使用top -p简单查看情况，发现在问题发生的时候，cpu使用率比平时高出80%，这个肯定是极为不正常的情况。同时，tail日志发现，在响应的1秒多的时间中，居然没有输出任何日志，随后处理响应的日志打印出来，处理时间仅仅为5ms。&lt;/p&gt;

&lt;p&gt;问题到这里，不得不怀疑是不是GC造成的STW，将线上程序重新部署至测试服中，使用jstat定时打出JVM Heap信息来观察情况。本以为是Full GC导致长时间STW，但在问题发生时，并没有发生Full GC，且老年代内存使用情况稳定。既然问题不出在Full GC，抱着侥幸的心里观察了Young GC的情况，这时发现了一处奇怪的地方。问题出现前，是临近Young GC的时候，问题出现时，Young GC连续发生了两次！但是，即使连续发生Young GC，GC时间相比起响应时间（1s）也是微不足道的。综合上述表象在这里做了一个推测，响应慢有可能是处理请求期间产生大量新对象引起的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://raw.githubusercontent.com/Piscesilent/pic-store/master/young-gc-pic-1.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;顺着思路走，这时候该怀疑JVM内存分配问题，用jmap查看Eden、From、To区，发现分配的内存是足够的。由于使用的是Parallel GC，尝试修改GC线程数，也无果。为了更好的验证问题的产生，测试程序打开了JMX，用JMC连接服务器将问题可视化。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://raw.githubusercontent.com/Piscesilent/pic-store/master/young-gc-pic-2.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;从图中可以清楚的看出问题所在，连续两次Young GC使得cpu占用率飙升。既然线上不能很好的找到问题，于是，选择线下找问题。&lt;/p&gt;

&lt;h2 id=&#34;二-线下问题分析&#34;&gt;二、线下问题分析&lt;/h2&gt;

&lt;p&gt;为了能在线下找到问题，我选择在问题发生的前后dump出JVM Heap信息到文件中。将文件导入到Eclipse Memory Analyzer中，并将文件信息进行比对。在这过程中，org.apache.catalina.webresources.StandardRoot类的实例大小变化引起了我的注意。经过几个文件的比对，该实例在Young GC前后retained heap变化有18M之多，在这里验证了前面的假设。&lt;/p&gt;

&lt;p&gt;StandardRoot类是对webapp提供资源的实现，继续往下看，在一个List中引用了大量的JarWarResourceSet对象，这些对象就是资源文件的集合，每个对象将引用的jar映射到&lt;code&gt;HashMap&amp;lt;String,JarEntry&amp;gt;&lt;/code&gt;中。接下来顺藤摸瓜，发现同一个JarWarResourceSet对象在GC前后retained heap变化很大，问题应该就出现在这里。再往下看，果然，在GC前后，类型为&lt;code&gt;HashMap&amp;lt;String,JarEntry&amp;gt;&lt;/code&gt;的对象被置空了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://raw.githubusercontent.com/Piscesilent/pic-store/master/young-gc-pic-3.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;既然问题已经定位到，这时候该看看源码这里是怎么引起置空问题的了。&lt;/p&gt;

&lt;h2 id=&#34;三-源码分析&#34;&gt;三、源码分析&lt;/h2&gt;

&lt;p&gt;首先从JarWarResourceSet类开始入手，在它的父类AbstractArchiveResourceSet中，找到了类型为&lt;code&gt;HashMap&amp;lt;String,JarEntry&amp;gt;&lt;/code&gt;的成员变量archiveEntries。将这个类的源码看了一遍，发现了这个方法：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@Override
public void gc() {
    synchronized (archiveLock) {
        if (archive != null &amp;amp;&amp;amp; archiveUseCount == 0) {
            try {
                archive.close();
            } catch (IOException e) {
                // Log at least WARN
            }
            archive = null;
            archiveEntries = null;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;当调用gc()时，会将archiveEntries置空。问题到这里就很清楚了，只要阻止调用gc()，那么问题就可以解决。在StandardRoot类中就可以找到了：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@Override
public void gc() {
    for (List&amp;lt;WebResourceSet&amp;gt; list : allResources) {
        for (WebResourceSet webResourceSet : list) {
            webResourceSet.gc();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;每当StandardRoot执行gc()时，会调用所有的ResourceSet中的gc()。继续跟踪源代码，在该类的backgroundProcess()方法中会调用gc()。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@Override
public void backgroundProcess() {
    cache.backgroundProcess();
    gc();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;从这个方法的注释中看出，该方法会被上下文周期性的调用。&lt;/p&gt;

&lt;p&gt;注意到getArchiveEntries()方法：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@Override
protected HashMap&amp;lt;String,JarEntry&amp;gt; getArchiveEntries(boolean single) {
    synchronized (archiveLock) {
        if (archiveEntries == null) {
            JarFile warFile = null;
            InputStream jarFileIs = null;
            archiveEntries = new HashMap&amp;lt;&amp;gt;();
            try {
                warFile = openJarFile();
                JarEntry jarFileInWar = warFile.getJarEntry(archivePath);
                jarFileIs = warFile.getInputStream(jarFileInWar);
                try (JarInputStream jarIs = new JarInputStream(jarFileIs)) {
                    JarEntry entry = jarIs.getNextJarEntry();
                    while (entry != null) {
                        archiveEntries.put(entry.getName(), entry);
                        entry = jarIs.getNextJarEntry();
                    }
                    setManifest(jarIs.getManifest());
                }
            } catch (IOException ioe) {
                ...
            } finally {
                ...
            }
        }
        return archiveEntries;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在请求到来时，当调用JarWarResourceSet中的getArchiveEntries()时，如果archiveEntries为空，则会重新去读取JarEntry，并放入archiveEntries中。读取Jar信息本身就是io操作，处理时间长，而在put到archiveEntries中时，又有可能因为产生新对象过多导致连续两次Young GC才能执行完getArchiveEntries()，所以导致该次请求时间过长。&lt;/p&gt;

&lt;h2 id=&#34;四-解决问题&#34;&gt;四、解决问题&lt;/h2&gt;

&lt;p&gt;既然问题已经找到，那么解决问题就很简单了。只需要重写StandardRoot的gc()，使其成为一个空实现就好了。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@Configuration
@EnableWebMvc
public class SpringMvcConfig extends WebMvcConfigurerAdapter {

    ......

    @Bean
    public EmbeddedServletContainerFactory servletContainer() {
        return new TomcatEmbeddedServletContainerFactory() {
            @Override
            protected void postProcessContext(Context context) {
                StandardRoot standardRoot = new StandardRoot(context) {
                    @Override
                    public void gc() {}
                };
                context.setResources(standardRoot);
            }
        };
    }

    ......

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

&lt;p&gt;最后测试打包上线，在线上运行稳定且问题解决。&lt;/p&gt;

&lt;h2 id=&#34;五-经验总结&#34;&gt;五、经验总结&lt;/h2&gt;

&lt;p&gt;在线上遇到问题时，快速通过jvm自带工具进行定位问题。如果问题棘手，保存现场快照离线进行更进一步分析。最后结合源码定位问题代码，最后将问题解决。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>MySQL 基准测试</title>
      <link>http://piscesilent.github.io/post/MySql_benchmark/</link>
      <pubDate>Thu, 01 Dec 2016 16:40:45 +0800</pubDate>
      
      <guid>http://piscesilent.github.io/post/MySql_benchmark/</guid>
      <description>

&lt;h2 id=&#34;前言说明&#34;&gt;前言说明&lt;/h2&gt;

&lt;p&gt;近期公司要求对线上使用的阿里云RDS做性能测试，经过一番调研，选用sysbench和mysqlslap这两个工具对线上数据库进行性能测试，本次测试数据库引擎均使用InnoDb。&lt;/p&gt;

&lt;p&gt;对于数据库测试，在各个性能指标中，应该着重关注以下几个点：&lt;/p&gt;

&lt;p&gt;(1)QPS(每秒Query量)&lt;/p&gt;

&lt;p&gt;每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准，即每秒的响应请求数，大致相当于最大吞吐能力。&lt;/p&gt;

&lt;p&gt;其公式为：QPS = Queries / seconds&lt;/p&gt;

&lt;p&gt;(2)TPS(每秒事务量)&lt;/p&gt;

&lt;p&gt;一台数据库服务器在单位时间内处理的事务的个数，在实际使用环境下，一般倾向于尽量使用简单的SQL语句，所以可以理解为数据库的增删改吞吐量。&lt;/p&gt;

&lt;p&gt;其公式为：TPS = (Com_commit + Com_rollback) / seconds&lt;/p&gt;

&lt;p&gt;(3) CPU/内存使用量&lt;/p&gt;

&lt;p&gt;在实际使用中，可以通过这两个数据指示数据库负荷，在压测中，要保证CPU满载，其压测结果才真实有效。&lt;/p&gt;

&lt;h2 id=&#34;sysbench&#34;&gt;sysbench&lt;/h2&gt;

&lt;p&gt;sysbench是一款开源的多线程性能测试工具，可以用于对数据库进行简单的性能测试。&lt;/p&gt;

&lt;p&gt;sysbench项目地址：&lt;a href=&#34;https://github.com/akopytov/sysbench&#34;&gt;https://github.com/akopytov/sysbench&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;sysbench的部参数说明如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;--oltp-table-size：指定表的行数
--mysql-host：数据库主机地址
--mysql-user：数据库账号，使用高权限帐号
--mysql-passwor：数据库密码
--mysql-table-engine：指定存储引擎，本次测试使用InnoDb
--mysql-db：指定在哪个数据库创建测试表
--test：指定Lua脚本, 本次使用官方默认oltp
--db-driver：指定数据库驱动
--num-threads：指定并发线程数
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在进行测试前，首先准备数据，使用以下命令进行数据的准备：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sysbench --test=oltp --oltp-table-size=10000 --oltp-table-name=test \
--mysql-table-engine=innodb \
--mysql-host=host \
--mysql-db=test \
--mysql-user=user \
--mysql-password=password \
--db-driver=mysql \
prepare
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;随后会在制定数据库中生成一张测试表，其基本结构和填充内容如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mysql&amp;gt; select * from test limit 1;
+----+---+---+----------------------------------------------------+
| id | k | c | pad                                                |
+----+---+---+----------------------------------------------------+
|  1 | 0 |   | qqqqqqqqqqwwwwwwwwwweeeeeeeeeerrrrrrrrrrtttttttttt |
+----+---+---+----------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后运行以下命令开始任务：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sysbench --test=oltp --oltp-table-size=10000 --oltp-table-name=test \
--mysql-table-engine=innodb \
--mysql-host=host \
--mysql-db=test \
--mysql-user=user \
--mysql-password=password \
--db-driver=mysql \
--num-threads=8 \
run
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;经过了一段时间的测试，其结果显示如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;OLTP test statistics:
    queries performed:
        read:                            140000
        write:                           50000
        other:                           20000
        total:                           210000
    transactions:                        10000  (951.74 per sec.)
    deadlocks:                           0      (0.00 per sec.)
    read/write requests:                 190000 (18083.06 per sec.)
    other operations:                    20000  (1903.48 per sec.)

Test execution summary:
    total time:                          10.5071s
    total number of events:              10000
    total time taken by event execution: 1996.0691
    per-request statistics:
         min:                                 40.74ms
         avg:                                199.61ms
         max:                               1304.29ms
         approx.  95 percentile:             491.18ms

Threads fairness:
    events (avg/stddev):           50.0000/4.50
    execution time (avg/stddev):   9.9803/0.10
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在输出的结果中，read表示总查询语句，write表示总增删改语句，其中transactions应该重点关注，括号内的数值即代表TPS。对于QPS而言，可以通过queries performed中的total参数除以Test execution summary中的total time获得。对于per-request statistics中的参数，可以重点关注avg（平均执行时间），以及approx.  95 percentile（95%置信区间内的语句执行时间的最大值）。&lt;/p&gt;

&lt;p&gt;最后使用以下语句清除临时数据：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sysbench --test=oltp --oltp-table-size=10000 --oltp-table-name=test \
--mysql-table-engine=innodb \
--mysql-host=host \
--mysql-db=test \
--mysql-user=user \
--mysql-password=password \
--db-driver=mysql \
cleanup
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在使用sysbench进行测试时，出现一些奇怪的现象，在使用官方脚本时，如果测试表行数过小，会出现很多死锁的情况。在这里建议尽量使用大表进行测试，其官方脚本在大表中的测试成绩和在小表中并无数量级的差距。&lt;/p&gt;

&lt;h2 id=&#34;mysqlslap&#34;&gt;mysqlslap&lt;/h2&gt;

&lt;p&gt;在使用了sysbench进行基准测试之后，应该对常用的语句进行一个基准测试，这类测试应根据每个系统实际使用情况，选取出具有代表性的语句进行有针对性的测试。这在指导开发工作中如何选用正确的SQL语句有相当大的作用。&lt;/p&gt;

&lt;p&gt;对于常用的单条语句测试，选用MySQL自带的工具mysqlslap，就可以很好的达到测试目的。在测试过程中，本次选用了数据库已经存在的表进行测试，对于没有这么大数据量的表的同学，以下给出一个创建测试表和测试数据的方法供大家参考：&lt;/p&gt;

&lt;p&gt;首先创建测试表：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CREATE TABLE `test_10k` (
  `id` VARCHAR(64) NOT NULL,
  `title` VARCHAR(255) DEFAULT &#39;&#39;,
  `del_flag` TINYINT(1) DEFAULT 0,
  `longitude` DOUBLE DEFAULT 0.0,
  `sum` int(11) DEFAULT &#39;0&#39;,
  `create_date` DATETIME DEFAULT NOW(),
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来使用存储过程生成测试数据：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;delimiter $$ 
SET AUTOCOMMIT = 0$$
create  procedure create_random_date_10k()  
begin
declare v_cnt decimal (10)  default 0; 
dd:loop            
        INSERT INTO test_10k VALUE         
        (REPLACE(UUID(),&#39;-&#39;,&#39;&#39;),CONCAT(&#39;测试&#39;,UUID()),0,RAND()*100,FLOOR(100000*RAND()),NOW()),       
        (REPLACE(UUID(),&#39;-&#39;,&#39;&#39;),CONCAT(&#39;测试&#39;,UUID()),0,RAND()*100,FLOOR(100000*RAND()),NOW()),        
        (REPLACE(UUID(),&#39;-&#39;,&#39;&#39;),CONCAT(&#39;测试&#39;,UUID()),0,RAND()*100,FLOOR(100000*RAND()),NOW()),
        (REPLACE(UUID(),&#39;-&#39;,&#39;&#39;),CONCAT(&#39;测试&#39;,UUID()),0,RAND()*100,FLOOR(100000*RAND()),NOW()),        
        (REPLACE(UUID(),&#39;-&#39;,&#39;&#39;),CONCAT(&#39;测试&#39;,UUID()),0,RAND()*100,FLOOR(100000*RAND()),NOW()),         
        (REPLACE(UUID(),&#39;-&#39;,&#39;&#39;),CONCAT(&#39;测试&#39;,UUID()),1,RAND()*100,FLOOR(100000*RAND()),NOW()),        
        (REPLACE(UUID(),&#39;-&#39;,&#39;&#39;),CONCAT(&#39;测试&#39;,UUID()),1,RAND()*100,FLOOR(100000*RAND()),NOW()),       
        (REPLACE(UUID(),&#39;-&#39;,&#39;&#39;),CONCAT(&#39;测试&#39;,UUID()),1,RAND()*100,FLOOR(100000*RAND()),NOW()),         
        (REPLACE(UUID(),&#39;-&#39;,&#39;&#39;),CONCAT(&#39;测试&#39;,UUID()),1,RAND()*100,FLOOR(100000*RAND()),NOW()),        
        (REPLACE(UUID(),&#39;-&#39;,&#39;&#39;),CONCAT(&#39;测试&#39;,UUID()),1,RAND()*100,FLOOR(100000*RAND()),NOW());                 
        commit;              
        set v_cnt = v_cnt+10;                            
            if  v_cnt = 10000 then leave dd;                           
            end if;          
        end loop dd ; 
end;$$ 
delimiter ;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;随后调用写好的存储过程：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;call create_random_date_10k;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;对于mysqlslap命令的使用，可以参考官方文档：&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://dev.mysql.com/doc/refman/5.6/en/mysqlslap.html&#34;&gt;http://dev.mysql.com/doc/refman/5.6/en/mysqlslap.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这边列出一些常用的参数：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;--concurrency：代表并发数量，多个可以用逗号隔开，concurrency=10,50,100, 并发连接线程数分别是10、50、100个并发。
--engines：代表要测试的引擎。
--iterations：代表要循环执行的次数。
--number-of-queries：一次测试代表执行语句的总数
--create-schema：需要测试的schema。
--query 使用自定义脚本执行测试，例如可以调用自定义的一个存储过程或者sql语句来执行测试。
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;例子如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mysqlslap -hhost -uuser -ppassword --concurrency=100 --iterations=1 --create-schema=&#39;test&#39; --query=&#39;select * from test_10k;&#39; --number-of-queries=10000 --debug-info
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其打印的结果如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Benchmark
    Average number of seconds to run all queries: 11.221 seconds
    Minimum number of seconds to run all queries: 11.221 seconds
    Maximum number of seconds to run all queries: 11.221 seconds
    Number of clients running queries: 100
    Average number of queries per client: 10


User time 4.62, System time 3.98
Maximum resident set size 123172, Integral resident set size 0
Non-physical pagefaults 155744, Physical pagefaults 11, Swaps 0
Blocks in 1944 out 0, Messages in 0 out 0, Signals 0
Voluntary context switches 153923, Involuntary context switches 5693
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果要计算QPS，可以通过（number-of-queries * iterations）除以结果中的Average number of seconds to run all queries秒数。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>MySQL索引优化总结（二）</title>
      <link>http://piscesilent.github.io/post/mysql_index_2/</link>
      <pubDate>Thu, 01 Sep 2016 00:15:23 +0800</pubDate>
      
      <guid>http://piscesilent.github.io/post/mysql_index_2/</guid>
      <description>

&lt;p&gt;在优化数据库索引中，了解到在新版本的MySQL中，有两个比较重要的新技术，Push down 和 Index merge。于是查了资料做了一些总结。&lt;/p&gt;

&lt;h1 id=&#34;push-down&#34;&gt;Push down&lt;/h1&gt;

&lt;p&gt;索引下推（Index Condition Pushdown）用于优化MySQL通过索引检索表的过程。&lt;/p&gt;

&lt;p&gt;如果没有ICP，存储引擎会遍历索引去定位表中的行，并返回给MySQL服务器。开启ICP后，如果在查询的WHERE条件中有部分条件可以使用索引，那么MySQL服务器会下推这部分索引到存储引擎，然后通过索引过滤的WHERE条件在存储引擎层进行数据过滤,而非将所有通过索引access到的结果传递到MySQL服务器层进行WHERE过滤。&lt;/p&gt;

&lt;p&gt;当使用Push down时：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;获取下一行的索引元组(但不是完整的行)。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;尝试只使用索引列检查是否符合条件。如果条件不满足,回到步骤1。只有满足条件才会回表读取数据。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果满足下推的索引条件，存储引擎通过索引元组定位和读取整行数据并返回给MySQL服务层。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;MySQL服务层评估没有被下推到存储引擎的WHERE条件，如果该行数据满足WHERE条件则使用，然后回到步骤1。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&#34;http://raw.githubusercontent.com/Piscesilent/pic-store/master/mysql_pushdown.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;如果没有使用该技术的情况下，会在读取索引元组之后直接在表中定位读取整行数据。&lt;/p&gt;

&lt;p&gt;在一个测试表中建立一个索引：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ALTER TABLE `cms_article` 
ADD INDEX `TEST_INDEX` (`audit_status` ASC, `create_by` ASC, `update_by` ASC);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;开启Push down后：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mysql&amp;gt; set optimizer_switch = &amp;quot;index_condition_pushdown=on&amp;quot;;
mysql&amp;gt; explain select * from cms_article where audit_status = 0 and update_by = &#39;1&#39;\G;
******** 1. row ********
           id: 1
  select_type: SIMPLE
        table: cms_article
         type: ref
possible_keys: TEST_INDEX
          key: TEST_INDEX
      key_len: 5
          ref: const
         rows: 735
        Extra: Using index condition
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;对比开启前：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mysql&amp;gt; set optimizer_switch = &amp;quot;index_condition_pushdown=off&amp;quot;;
mysql&amp;gt; explain select * from cms_article where audit_status = 0 and update_by = &#39;1&#39;\G;
******** 1. row ********
           id: 1
  select_type: SIMPLE
        table: cms_article
         type: ref
possible_keys: TEST_INDEX
          key: TEST_INDEX
      key_len: 5
          ref: const
         rows: 735
        Extra: Using where
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看出即使搜索条件不符合联合索引的最左匹配原则，但是MySQL还是对搜索进行了优化。&lt;/p&gt;

&lt;h1 id=&#34;index-merge&#34;&gt;Index merge&lt;/h1&gt;

&lt;p&gt;在多个列上建立独立的单列索引在之前并不会提高查询速度，因为每次查询只会使用到一个索引。但是在索引合并（Index merge）技术下，可以做到最大限度利用多个索引定位行。&lt;/p&gt;

&lt;p&gt;索引合并有三种情况：OR条件的Union，AND条件的Intersection，OR和AND条件的组合Union&amp;amp;Intersection。Index merge其实就是分别通过对两个独立的索引进行过滤之后，将过滤之后的结果聚合在一起，然后在返回结果集。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mysql&amp;gt; explain select * from cms_article where proprietary_id = &#39;345345&#39; or del_flag = 0 and audit_status = 0 \G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: cms_article
         type: index_merge
possible_keys: INDEX_PRO,INDEX_DEL_AUDIT
          key: INDEX_PRO,INDEX_DEL_AUDIT
      key_len: 259,10
          ref: NULL
         rows: 734
        Extra: Using union(INDEX_PRO,INDEX_DEL_AUDIT); Using where
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其type表明使用了索引合并，且在Extra中详细说明了合并方式。这里的OR条件就是利用了一个union方式的索引合并，将 &lt;code&gt;INDEX_PRO&lt;/code&gt; 和 &lt;code&gt;INDEX_DEL_AUDIT&lt;/code&gt; 合并后共同计算返回结果。&lt;/p&gt;

&lt;p&gt;虽然是一个优化技术，但索引合并更多时候说明了表上的索引创建有问题，在《高性能MySQL》中给出了理由：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;当对多个索引进行相交操作时，通常需要一个包含所有相关项的多列索引，而不是多个独立的单个索引。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;当对多个索引进行联合操作时，需要耗费大量资源进行索引合并，特别是索引选择性不高的情况下，需要扫描并返回大量的数据进行计算。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在并发大的情况下谨慎使用Index merge。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;参考文档&#34;&gt;参考文档：&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html&#34; title=&#34;https://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html&#34;&gt;https://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html
&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;《高性能MySQL（第三版）》&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>MySQL索引优化总结（一）</title>
      <link>http://piscesilent.github.io/post/mysql_index_1/</link>
      <pubDate>Tue, 30 Aug 2016 11:53:21 +0800</pubDate>
      
      <guid>http://piscesilent.github.io/post/mysql_index_1/</guid>
      <description>

&lt;h1 id=&#34;explain命令&#34;&gt;Explain命令&lt;/h1&gt;

&lt;p&gt;explain用于获取查询相关的执行计划信息，在这个命令中可以知晓查询是如何执行的。&lt;/p&gt;

&lt;p&gt;使用explain也很简单，只需要在所需分析的语句前加上explain执行即可。其输出结果如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;******** 1. row ********
           id: 1
  select_type: SIMPLE
        table: cms_article
         type: ref
possible_keys: INDEX_CREATE
          key: INDEX_CREATE
      key_len: 259
          ref: const
         rows: 1
        Extra: Using index condition
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;id&#34;&gt;id&lt;/h2&gt;

&lt;p&gt;这一列用于标识查询编号，如果没有子查询，那么每个索引都会标识为1。&lt;/p&gt;

&lt;h2 id=&#34;select-type&#34;&gt;select_type&lt;/h2&gt;

&lt;p&gt;这行标识了SELECT的类型，即简单查询还是复杂查询&lt;/p&gt;

&lt;p&gt;SIMPLE：查询中不包含子查询和UNION查询，即简单查询&lt;/p&gt;

&lt;p&gt;对于复杂查询&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;explain select v.id from (select id from cms_article where create_by = &#39;1&#39;) v \G;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;explain后会出现多行数据&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;************ 1. row ***********
           id: 1
  select_type: PRIMARY
        table: &amp;lt;derived2&amp;gt;
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 2
        Extra: NULL
************ 2. row ************
           id: 2
  select_type: DERIVED
        table: cms_article
         type: ref
possible_keys: INDEX_CREATE
          key: INDEX_CREATE
      key_len: 259
          ref: const
         rows: 1
        Extra: Using where; Using index
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最外一层总是PRIMARY。&lt;/p&gt;

&lt;p&gt;PRIMARY：表示复杂查询的最外层&lt;/p&gt;

&lt;p&gt;DERIVED：用于表示包含在FROM子句中的子查询&lt;/p&gt;

&lt;p&gt;UNION：UNION中的第二个或后面的SELECT语句&lt;/p&gt;

&lt;p&gt;SUBQUERY：子查询中的SELECT（不在FROM子句中）&lt;/p&gt;

&lt;h2 id=&#34;table&#34;&gt;table&lt;/h2&gt;

&lt;p&gt;用于标识查询对应的表，结合id可以看出查询的关联顺序&lt;/p&gt;

&lt;h2 id=&#34;type&#34;&gt;type&lt;/h2&gt;

&lt;p&gt;这个属性决定了MySQL如何查找表中的行，也是索引优化的中应该关注的最重要的属性。从差到优，依次为：&lt;/p&gt;

&lt;h3 id=&#34;all&#34;&gt;all&lt;/h3&gt;

&lt;p&gt;代表全表扫描，通常是最差的情况&lt;/p&gt;

&lt;h3 id=&#34;index&#34;&gt;index&lt;/h3&gt;

&lt;p&gt;按索引次序读取表中数据，最差的情况是按索引的次序读取整个表的数据，这样读取的开销会相当大，因为此时是按照随机的次序去取表中的数据，甚至于比全表扫描效率还低。&lt;/p&gt;

&lt;h3 id=&#34;range&#34;&gt;range&lt;/h3&gt;

&lt;p&gt;范围扫描，即有限制的INDEX扫描，通常是带有范围搜索条件时出现。&lt;/p&gt;

&lt;p&gt;在通常情况下，优化器会在索引存在的情况下，通过符合 RANGE 范围的条数和总数的比例来选择是使用索引还是进行全表遍历。当需要读取的数据超过一个临界值时，优化器会放弃从索引中读取而改为进行全表扫描，这是为了避免过多的 random disk。&lt;/p&gt;

&lt;p&gt;所以有时候会看到使用范围查询时，即使在该字段上建有索引，其type却是ALL的原因。&lt;/p&gt;

&lt;h3 id=&#34;ref&#34;&gt;ref&lt;/h3&gt;

&lt;p&gt;索引查找，返回的是所有匹配某个单值的行，一般而言在优化得当的SQL查询中，其访问方式多为ref。&lt;/p&gt;

&lt;h3 id=&#34;eq-ref&#34;&gt;eq_ref&lt;/h3&gt;

&lt;p&gt;和ref类似，只有使用唯一索引或使用主键查找才会出现，其效率比ref更高。&lt;/p&gt;

&lt;h3 id=&#34;const-system&#34;&gt;const, system&lt;/h3&gt;

&lt;p&gt;说明MySQL能将查询转换为常量&lt;/p&gt;

&lt;h3 id=&#34;null&#34;&gt;NULL&lt;/h3&gt;

&lt;p&gt;MySQL能在优化阶段分解查询语句，执行阶段甚至不用再访问表或索引。&lt;/p&gt;

&lt;h2 id=&#34;possible-keys&#34;&gt;possible_keys&lt;/h2&gt;

&lt;p&gt;指出本次查询可以使用哪些索引，这一列是在优化过程早期创建的，有些索引对于后续的优化过程是没用的。&lt;/p&gt;

&lt;h2 id=&#34;key&#34;&gt;key&lt;/h2&gt;

&lt;p&gt;显示MySQL实际决定使用的索引。如果没有选择索引，键是NULL。&lt;/p&gt;

&lt;h2 id=&#34;key-len&#34;&gt;key_len&lt;/h2&gt;

&lt;p&gt;显示了MySQL在索引里使用的字节数。&lt;/p&gt;

&lt;h2 id=&#34;ref-1&#34;&gt;ref&lt;/h2&gt;

&lt;p&gt;显示使用哪个列（或常数）与key一起从表中选择行。&lt;/p&gt;

&lt;h2 id=&#34;rows&#34;&gt;rows&lt;/h2&gt;

&lt;p&gt;MySQL估计为了找到所需的行而要读取的行数。这个值虽然不是最终要从表里读取数据的行数，但是也能反映出索引对查询条件的覆盖率。&lt;/p&gt;

&lt;h2 id=&#34;extra&#34;&gt;Extra&lt;/h2&gt;

&lt;p&gt;这里显示了一些重要的额外信息，对于索引优化工作，首先需要关注以下几个额外信息：&lt;/p&gt;

&lt;h3 id=&#34;using-index&#34;&gt;Using index&lt;/h3&gt;

&lt;p&gt;当在Extra列中显示这个信息时，这表明此次查询没有访问表，因为在索引中已经有足够的数据。所以，可以把这个当作&amp;rdquo;Using index only&amp;rdquo;。这里有一点需要注意的是，当使用聚集索引时（通常是主键的默认索引），尽管查询只扫描了索引，但在Extra中并不会显示&amp;rdquo;Using index&amp;rdquo;。&lt;/p&gt;

&lt;h3 id=&#34;using-index-condition&#34;&gt;Using index condition&lt;/h3&gt;

&lt;p&gt;当出现这个信息时表明，本次查询首先会访问索引进行搜索过滤，然后从索引中得到结果后再进行回表查询。这里使用了push down技术，具体可以参考官方文档：
&lt;a href=&#34;https://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html&#34;&gt;https://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>跨域资源共享（CORS）介绍及在Spring MVC with Shiro 的应用</title>
      <link>http://piscesilent.github.io/post/CORS/</link>
      <pubDate>Tue, 05 Jul 2016 09:07:42 +0800</pubDate>
      
      <guid>http://piscesilent.github.io/post/CORS/</guid>
      <description>

&lt;p&gt;CORS，即跨域资源共享（Cross-Origin Resource Sharing）
跨站HTTP请求是指发起请求的资源所在域不同于该请求所指向资源所在的域的HTTP请求。&lt;/p&gt;

&lt;p&gt;CORS旨在定义一种规范让浏览器在接收到从提供者获取的资源时能够正决定是否应该将此资源分发给消费者作进一步处理。CROS利用资源提供者的显式授权来决定目标资源是否应该与消费者共享。&lt;/p&gt;

&lt;p&gt;JavaScript出于安全方面的考虑，不允许跨域调用其他页面的对象，其同源策略说明如下：&lt;/p&gt;

&lt;table&gt;
    &lt;tr&gt;
        &lt;th&gt;链接&lt;/th&gt;
        &lt;th&gt;说明&lt;/th&gt;
        &lt;th&gt;是否跨域&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;www.a.com/a&lt;/p&gt;&lt;p&gt;www.a.com/a&lt;/p&gt;&lt;/td&gt;
        &lt;td&gt;同一域名&lt;/td&gt;
        &lt;td&gt;否&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;www.a.com/a&lt;/p&gt;&lt;p&gt;www.a.com/b&lt;/p&gt;&lt;/td&gt;
        &lt;td&gt;同一域名下的不同目录&lt;/td&gt;
        &lt;td&gt;否&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;www.a.com:8080/a&lt;/p&gt;&lt;p&gt;www.a.com:8081/a&lt;/p&gt;&lt;/td&gt;
        &lt;td&gt;同一域名下的不同端口&lt;/td&gt;
        &lt;td&gt;是&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;www.a.com/a&lt;/p&gt;&lt;p&gt;api.a.com/a&lt;/p&gt;&lt;/td&gt;
        &lt;td&gt;子域不同&lt;/td&gt;
        &lt;td&gt;是&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;www.a.com/a&lt;/p&gt;&lt;p&gt;www.b.com/a&lt;/p&gt;&lt;/td&gt;
        &lt;td&gt;不同域名&lt;/td&gt;
        &lt;td&gt;是&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h1 id=&#34;一-cors相关&#34;&gt;一、CORS相关&lt;/h1&gt;

&lt;p&gt;当一个现代浏览器制图要去进行跨域请求时（method为GET、HEAD时除外），浏览器会发送一个带有Origin参数的OPTIONS请求，即预检查请求，这个参数值就是当前域的域名。比如，当从
    &lt;a href=&#34;http://www.a.com&#34;&gt;http://www.a.com&lt;/a&gt;
尝试去访问
    www.b.com
下的资源时，&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Origin:http://www.a.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;将会附加在发往www.b.com的请求头上&lt;/p&gt;

&lt;p&gt;当服务端接受到这个OPTIONS请求时，会有三种处理方式：&lt;/p&gt;

&lt;p&gt;1、不允许进行跨域资源请求&lt;/p&gt;

&lt;p&gt;2、允许所有的域进行资源访问，即在响应头上加入：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Access-Control-Allow-Origin: *
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果这样做，处于安全性的考虑，响应头上将不能携带cookie，所以一般的情况应该是这样：&lt;/p&gt;

&lt;p&gt;3、在响应头上声明可以进行访问的域：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Access-Control-Allow-Origin: http://www.a.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样，www.a.com就可以请求到www.b.com的资源。&lt;/p&gt;

&lt;p&gt;除此之外，与CORS相关的请求头信息还有：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Access-Control-Request-Method
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个请求头的作用在于指定接下来的跨域请求中的方法。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Access-Control-Request-Header
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个是指定跨域请求时所携带的自定义头信息。&lt;/p&gt;

&lt;p&gt;与CORS相关的响应体信息还有：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Access-Control-Allow-Methods
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个响应头的作用在于指定在接下来的真正的请求中被允许的方法（Method），比如在这里设置为&amp;rdquo;POST&amp;rdquo;，如果接下来的请求是PUT方法，那么将会失败。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Access-Control-Allow-Headers
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个是指请求中所允许携带的请求头信息。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Access-Control-Max-Age
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以指定缓存时间，避免浏览器频繁发送OPTIONS请求。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Access-Control-Allow-Credentials
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个响应头用来表明服务端是否支持用户凭证，比如Cookie、HTTP-Authentication、证书等。如果需要，则设置此值为true。&lt;/p&gt;

&lt;h1 id=&#34;二-spring-mvc-中的应用&#34;&gt;二、Spring MVC 中的应用&lt;/h1&gt;

&lt;p&gt;在实际Spring MVC + Shiro 项目中，由于进行前后端分离改造不可避免的要遇到跨域问题。&lt;/p&gt;

&lt;p&gt;首先，先要解决的是Spring MVC对OPTIONS请求拦截的问题。在Spring MVC servlet配置中，添加&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;init-param&amp;gt;
    &amp;lt;param-name&amp;gt;dispatchOptionsRequest&amp;lt;/param-name&amp;gt;
    &amp;lt;param-value&amp;gt;true&amp;lt;/param-value&amp;gt;
&amp;lt;/init-param&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;允许DispatcherServlet分发器分发Options请求。&lt;/p&gt;

&lt;p&gt;在配置bean时，需要注意的是shiro也是会拦截OPTIONS请求的。由于OPTIONS请求中没有携带任何cookie信息，shiro直接会认为该请求没有权限进而拦截在外。所以，在shiro中要自定义一个filter，用于排除OPTIONS请求。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public class CorsUserAuthenticationFilter extends UserFilter {
    /**
     * 不过滤OPTIONS方法
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest httpRequest = WebUtils.toHttp(request);
        if (&amp;quot;OPTIONS&amp;quot;.equalsIgnoreCase(httpRequest.getMethod())) {
            return true;
        }

        return super.isAccessAllowed(request, response, mappedValue);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在这里继承了shiro自带的UserFilter，并重写isAccessAllowed函数，如果请求的Method为OPTIONS时，返回true，表示不过滤。&lt;/p&gt;

&lt;p&gt;经过上述两步，OPTIONS请求我们就可以在程序中真正拿到并且可以控制其响应头参数，达到想要的目的。&lt;/p&gt;

&lt;p&gt;一开始在项目中使用比较原始的方法，在复杂请求的同名接口上新增一个OPTIONS请求处理：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@RequestMapping(value = &amp;quot;&amp;quot;, method = RequestMethod.OPTIONS)
public HttpEntity optionsHandle(AppUser appUser, HttpServletRequest request,
        HttpServletResponse response) {
    MultiValueMap&amp;lt;String, String&amp;gt; map = new LinkedMultiValueMap&amp;lt;String, String&amp;gt;();
    String origin = request.getHeader(&amp;quot;Origin&amp;quot;);
    map.add(&amp;quot;Access-Control-Allow-Credentials&amp;quot;, &amp;quot;true&amp;quot;);
    map.add(&amp;quot;Access-Control-Allow-Origin&amp;quot;, origin);
    map.add(&amp;quot;Access-Control-Allow-Methods&amp;quot;, &amp;quot;POST,GET,OPTIONS,DELETE,PUT,OPTIONS&amp;quot;);
    map.add(&amp;quot;Access-Control-Allow-Headers&amp;quot;, &amp;quot;X-Requested-With,Content-Type,Accept&amp;quot;);
    HttpEntity he = new HttpEntity(map);
    return he;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;要注意的是@RequestMapping中的value要和复杂请求的value一致，即要保证他们俩的URL是一致的。在这边我只是为了方便调试才将请求的Origin直接放入
    Access-Control-Allow-Origin
中。在部署的时候，如果为了安全性考虑，可以将这个值写死，指定特定的域名才能跨域请求资源。&lt;/p&gt;

&lt;p&gt;当时测试通过后在考虑到项目中这么多接口，是否应该换一种方式进行处理更为妥当。考虑之后决定采用拦截器的方式实现。&lt;/p&gt;

&lt;p&gt;在Spring mvc的xml中添加：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;mvc:interceptors&amp;gt;
    &amp;lt;mvc:interceptor&amp;gt;
        &amp;lt;mvc:mapping path=&amp;quot;/**&amp;quot; /&amp;gt;
        &amp;lt;bean class=&amp;quot;xxx.interceptor.CorsRequestInterceptor&amp;quot; /&amp;gt;
    &amp;lt;/mvc:interceptor&amp;gt;
&amp;lt;/mvc:interceptors&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里配置了一个全地址匹配的拦截器，拦截器的实现如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public class CorsRequestInterceptor implements HandlerInterceptor {

    String origin = &amp;quot;&amp;quot;;

    @Override
    public void afterCompletion(HttpServletRequest arg0,
            HttpServletResponse arg1, Object arg2, Exception arg3)
            throws Exception {
    }

    @Override
    public void postHandle(HttpServletRequest arg0, HttpServletResponse response,
            Object arg2, ModelAndView arg3) throws Exception {
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object arg2) throws Exception {
        origin = request.getHeader(&amp;quot;Origin&amp;quot;);
        response.addHeader(&amp;quot;Access-Control-Allow-Credentials&amp;quot;,&amp;quot;true&amp;quot;);
        response.addHeader(&amp;quot;Access-Control-Allow-Origin&amp;quot;, origin);
        response.addHeader(&amp;quot;Access-Control-Allow-Methods&amp;quot;, &amp;quot;POST,GET,DELETE,PUT,OPTIONS&amp;quot;);
        response.addHeader(&amp;quot;Access-Control-Allow-Headers&amp;quot;, &amp;quot;X-Requested-With,Content-Type,Accept&amp;quot;);
        return true;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样，可以在所有的响应中都加上跨域信息。&lt;/p&gt;

&lt;p&gt;对于OPTIONS方法的处理，通过一个匹配全地址的Controller进行处理：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@Controller
public class OptionsMethodController {
    @RequestMapping(value = {&amp;quot;/**&amp;quot;}, method = RequestMethod.OPTIONS)
    public void saveOptions(HttpServletRequest resquest,HttpServletResponse response) {
        response.setStatus(200);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;通过以上配置，就可以实现全接口的跨域处理。&lt;/p&gt;

&lt;p&gt;Ps：在Spring MVC 4.2以上版本中还可以通过以下方式实现：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@Configuration
public class MigratedConfiguration {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping(&amp;quot;/**&amp;quot;).allowedOrigins(&amp;quot;http://www.a.com&amp;quot;);
            }
        };
    }
}
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>关于Java异常机制设计</title>
      <link>http://piscesilent.github.io/post/Java_exception/</link>
      <pubDate>Sun, 29 May 2016 10:35:37 +0800</pubDate>
      
      <guid>http://piscesilent.github.io/post/Java_exception/</guid>
      <description>&lt;p&gt;转载请注明：&lt;a href=&#34;http://piscesilent.github.io/2016/05/29/关于java异常机制设计&#34;&gt;http://piscesilent.github.io/2016/05/29/关于java异常机制设计&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Java不同于C，它提供了一套相对完善的异常及其处理体系。最近在工作中给公司项目设计一套异常处理机制，这里简单记录一下一些容易忽略的基本点。&lt;/p&gt;

&lt;p&gt;层次图如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;|--java.lang.Object
|   |
|   |--java.lang.Throwable
|       |
|       |--java.lang.Exception
|       |    |
|       |    |--java.lang.RuntimeException
|       |
|       |--java.lang.Error
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类（或其子类之一）的实例时，才能通过 JVM 或者 throw 语句抛出。类似地，只有此类或其子类之一才可以是 catch 子句中的参数类型。&lt;/p&gt;

&lt;p&gt;显然，可以根据Throwable的两个重要子类划分出两种异常：Error和Exception。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Error&lt;/p&gt;

&lt;p&gt;An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions. The ThreadDeath error, though a &amp;ldquo;normal&amp;rdquo; condition, is also a subclass of Error because most applications should not try to catch it.&lt;/p&gt;

&lt;p&gt;A method is not required to declare in its throws clause any subclasses of Error that might be thrown during the execution of the method but not caught, since these errors are abnormal conditions that should never occur. That is, Error and its subclasses are regarded as &lt;strong&gt;unchecked exceptions&lt;/strong&gt; for the purposes of compile-time checking of exceptions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;从源码的注释中可以看出，Error初衷设计时是不希望被捕获的，因为导致抛出Error的原因都是非正常的原因，比如硬件、系统、JVM等出现了不可预测的错误，在程序中即使捕获了也不能进行修复。如果程序抛出Error，绝大部分的问题都不会出在代码上。&lt;/p&gt;

&lt;p&gt;注意到以上的加粗文字，Error是一种unchecked exceptions，即不需要显式处理的异常。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Exception&lt;/p&gt;

&lt;p&gt;The class Exception and its subclasses are a form of Throwable that indicates conditions that a reasonable application might want to catch.&lt;/p&gt;

&lt;p&gt;The class Exception and any subclasses that are not also subclasses of RuntimeException are &lt;strong&gt;checked exceptions&lt;/strong&gt;. Checked exceptions need to be declared in a method or constructor&amp;rsquo;s throws clause if they can be thrown by the execution of the method or constructor and propagate outside the method or constructor boundary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;从注释中看出，Exception是一种期望被捕获的异常，除了RuntimeException以外的Exception，都必须在程序中处理，要不在定义方法时抛出，要不在方法中捕获并进行处理，也就是常见的try-catch-finally代码块。&lt;/p&gt;

&lt;p&gt;着重的文字中显示，Exception(除RuntimeException以外)，是一种checked exceptions，换言之，是一种需要显式处理的异常。抛出这类异常的意图是设计者认为这些异常是可以被恢复的，上层使用者捕获到这些异常时，有能力将程序恢复并继续执行。&lt;/p&gt;

&lt;p&gt;从源码注释中有个疑问，为什么RuntimeException不同于其他Exception，我们来看一下JAVA设计者对RuntimeException的设计意图。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;RuntimeException&lt;/p&gt;

&lt;p&gt;RuntimeException is the superclass of those exceptions that can be thrown during the normal operation of the Java Virtual Machine.&lt;/p&gt;

&lt;p&gt;RuntimeException and its subclasses are &lt;strong&gt;unchecked exceptions&lt;/strong&gt;. Unchecked exceptions do not need to be declared in a method or constructor&amp;rsquo;s throws clause if they can be thrown by the execution of the method or constructor and propagate outside the method or constructor boundary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;从文档中可以看出，RuntimeException是一种在JVM正常运行的情况下抛出的异常，是一种运行时期不可恢复的异常。虽然和Error一样同属于unchecked exceptions，但是设计者并没有强调该类异常不希望被捕获(should not try to catch)，所以在必要情况下还是推荐捕获该类异常，并且记录到日志中，以便分析，加强代码鲁棒性。出现该类异常很大一部分是由于业务代码不规范造成的。&lt;/p&gt;

&lt;p&gt;常见的RuntimeException包括：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;NullPointerException - 空指针引用异常&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;ClassCastException - 类型强制转换异常&lt;/li&gt;
&lt;li&gt;IllegalArgumentException - 传递非法参数异常&lt;/li&gt;
&lt;li&gt;ArithmeticException - 算术运算异常&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;ArrayStoreException - 向数组中存放与声明类型不兼容对象异常&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;IndexOutOfBoundsException - 下标越界异常&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;等等。&lt;/p&gt;

&lt;p&gt;在平时使用异常机制时尽量做到以下几点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;在捕获异常时不要因为贪图方便，直接捕获Throwable异常。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    try {
        //TODO
    } catch (Throwable e) { //直接捕获Throwable，不可取
        //TODO
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;尽量在try-catch中捕获多种类型的异常，并对这些异常做不同的处理。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    try {
        //TODO
    } catch (ServiceException e) { //自定义异常1
        //TODO
    } catch (ConsumerException e) {  //自定义异常2
        //TODO
    } 
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在抛出异常时，尽量细化抛出的异常，便于捕获及代码可读性。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在设计自定义异常时，尽量考虑抛出unchecked exceptions,即RuntimeException及其子类(Error及其子类一般不做考虑)。不要为逻辑代码抛出checked exceptions，除非这个异常非处理不可，否则，调用者需要经常捕获这些早已知道不可能发生的异常。&lt;/p&gt;

&lt;p&gt;自定义异常也不要直接继承Throwable，因为异常分类已经相当明确了。在Java自己类库中,除了Error和Exception，没有异常是直接继承自Throwable的。&lt;/p&gt;

&lt;p&gt;所以一般在设计自定义异常时，多数情况下请继承RuntimeException或其子类。事实上，多数框架设计异常时也是根据这一原则设计的。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public class SomeoneYouWantException extends RuntimeException {
    //TODO
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;尽量将异常统一抛给上层调用者，底层只负责向上抛异常，由上层调用者统一之时如何进行处理。如果在每个出现异常的地方都直接进行处理，会导致程序异常处理流程混乱，不利于后期维护和异常错误排查。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;善用finally，在异常处理体系中，finally总是会被执行，除了以下3种情况：&lt;/p&gt;

&lt;p&gt;1）在try中执行，System.exit(0)终止JVM的运行&lt;/p&gt;

&lt;p&gt;2）当一个线程在执行 try 语句块或者 catch 语句块时被打断（interrupted）或者被终止（killed）&lt;/p&gt;

&lt;p&gt;3）当一个线程在执行 try 语句块或者 catch 语句块时出现不可抗力因素导致计算机终止运行&lt;/p&gt;

&lt;p&gt;面对第一种情况时可以使用Runtime.getRuntime().addShutdownHook()添加钩子，执行本该在finally中执行的语句，剩余两种情况只能通过其他手段避免。&lt;/p&gt;

&lt;p&gt;所以在finally中关闭流或释放资源是一种良好的习惯，当然JDK 7提供了一个更为便捷的try-with-resources机制用于关闭资源。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ps：在设计时关于如何选择unchecked exceptions和checked exceptions这两种异常，在&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;http://blog.csdn.net/yanquan345/article/details/19633623&#34;&gt;http://blog.csdn.net/yanquan345/article/details/19633623&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;中讲得很详细，截取如下：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;何时选用编译时异常：编译时异常是Java特有的，其它语言没有，刚出来时很流行，所以你可以看到流处理包里充斥着IOException，但经过多年的使用，有人觉得编译时异常是一种实验性错误，应该完全丢弃，说这个话的人就是《Think In Java》的一书的作者Eckel，我认为这种说法太绝对了，关于这个是与否也有很大的争论。《Effective Java》一书的作者则认为应避免不必要的编译时异常，因为你抛编译时异常会给强制要求调用者捕获，这会增加他的负担，我是这一观点的支持者。那到底何时抛编译时异常呢？当你发现一个异常情况时，检查这两个条件，为真时选用编译时异常：一、如果调用者可以恢复此异常情况，二、如果调用者不能恢复，但能做出有意义的事，如转译等。如果你不确定调用者能否做出有意义的事，就别使编译时异常，免得被抱怨。还有一条原则，应尽最大可能使用编译时异常来代替错误码，这条也是编译时异常设计的目的。另外，必须注意使用编译时异常的目的是为了恢复执行，所以设计异常类的时候，应提供尽量多的异常数据，以便于上层恢复，比如一个解析错误，可以在设计的异常类写几个变量来存储异常数据：解析出错的句子的内容，解析出错句子的行号，解析出错的字符在行中的位置。这些信息可能帮助调用恢复程序。&lt;/p&gt;

&lt;p&gt;何时选用运行时异常：首先，运行时异常肯定是不可恢复的异常，否则按上段方法处理。这个不可恢复指的是运行时期不可恢复，如果可以修改源代码来避免本异常的发生呢，那说明这是一个编程错误，对于编程错误，一定要抛运行时异常，编程错误一般可以通过修改代码来永久性避免该异常，所以这种情况应该让程序挂掉，相当于爆出一个bug，从而提醒程序员修改代码。这种编程错误可以总结一下，API是调用者与实现者之间的契约，调用者必须遵守契约，比如传入的参数不允许为空，这一点是隐含契约，没必要明确写出来的，如果违反契约，实现者就可以抛运行时异常，让程序挂掉以提醒调用者。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在设计异常时可以借鉴。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elasticsearch 简单入门（一）</title>
      <link>http://piscesilent.github.io/post/Elasticsearch1/</link>
      <pubDate>Tue, 24 May 2016 19:49:42 +0800</pubDate>
      
      <guid>http://piscesilent.github.io/post/Elasticsearch1/</guid>
      <description>

&lt;h1 id=&#34;you-know-for-search&#34;&gt;You Know, for Search&lt;/h1&gt;

&lt;p&gt;Elasticsearch是一个实时的分布式搜索和分析引擎。它可以用于全文搜索，结构化搜索以及分析。&lt;/p&gt;

&lt;p&gt;它是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎，基于RESTful web接口设计。&lt;/p&gt;

&lt;p&gt;Elasticsearch是面向文档(document oriented)的，这意味着它可以存储整个对象或文档(document)。&lt;/p&gt;

&lt;p&gt;Elasticsearch集群可以包含多个索引(indices)（数据库），每一个索引可以包含多个类型(types)（表），每一个类型包含多个文档(documents)（行），然后每个文档包含多个字段(Fields)（列）。&lt;/p&gt;

&lt;p&gt;其对应关系如下表：&lt;/p&gt;

&lt;table&gt;
    &lt;tr&gt;
        &lt;td&gt;MySQL&lt;/td&gt;
        &lt;td&gt;Databases&lt;/td&gt;
        &lt;td&gt;Tables&lt;/td&gt;
        &lt;td&gt;Rows&lt;/td&gt;
        &lt;td&gt;Colums&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;Elasticsearch&lt;/td&gt;
        &lt;td&gt;Indices&lt;/td&gt;
        &lt;td&gt;Types&lt;/td&gt;
        &lt;td&gt;Documents&lt;/td&gt;
        &lt;td&gt;Fields&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;简单部署：&lt;/p&gt;

&lt;p&gt;1、在以下地址中&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.elastic.co/downloads/elasticsearch&#34;&gt;https://www.elastic.co/downloads/elasticsearch&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;下载最新的Es，Unzip&lt;/p&gt;

&lt;p&gt;2、&lt;/p&gt;

&lt;p&gt;执行：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sh ./bin/elasticsearch
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;默认RESTful API端口为9200,默认JAVA API端口为9300。&lt;/p&gt;

&lt;p&gt;3、在控制台中输入：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;curl -XGET &amp;quot;http://127.0.0.1:9200/&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;输出：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;Alibar&amp;quot;,
  &amp;quot;cluster_name&amp;quot;: &amp;quot;elasticsearch&amp;quot;,
  &amp;quot;version&amp;quot;: {
    &amp;quot;number&amp;quot;: &amp;quot;2.2.0&amp;quot;,
    &amp;quot;build_hash&amp;quot;: &amp;quot;8ff36d139e16f8720f2947ef62c8167a888992fe&amp;quot;,
    &amp;quot;build_timestamp&amp;quot;: &amp;quot;2016-01-27T13:32:39Z&amp;quot;,
    &amp;quot;build_snapshot&amp;quot;: false,
    &amp;quot;lucene_version&amp;quot;: &amp;quot;5.4.1&amp;quot;
  },
  &amp;quot;tagline&amp;quot;: &amp;quot;You Know, for Search&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;BINGO&lt;/p&gt;

&lt;p&gt;elasticsearch 单机环境就已经配置成功了。&lt;/p&gt;

&lt;p&gt;Ps：关于&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;./config/elasticsearch.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;的简单配置说明：&lt;/p&gt;

&lt;p&gt;1、&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cluster.name
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;集群名，默认是elasticsearch，用于区分在同一网段下的不同elasticsearch集群。&lt;/p&gt;

&lt;p&gt;2、&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;network.bind_host
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;绑定的ip地址，默认为0.0.0.0&lt;/p&gt;

&lt;p&gt;3、&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;network.publish_host
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;节点发现地址，一般配置成内网地址，用于节点间的互相发现、通讯&lt;/p&gt;

&lt;p&gt;4、&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;network.host
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;用于同时设置&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;network.bind_host
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;和&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;network.publish_host
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
  </channel>
</rss>