<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Goffery's Blog</title>
    <description>Every failure is leading towards success.</description>
    <link>http://offery-gong.github.io/</link>
    <atom:link href="http://offery-gong.github.io/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Fri, 06 Sep 2019 02:04:32 +0000</pubDate>
    <lastBuildDate>Fri, 06 Sep 2019 02:04:32 +0000</lastBuildDate>
    <generator>Jekyll v3.8.5</generator>
    
      <item>
        <title>缓存</title>
        <description>&lt;h3 id=&quot;缓存的特征&quot;&gt;缓存的特征&lt;/h3&gt;

&lt;h4 id=&quot;命中率&quot;&gt;命中率&lt;/h4&gt;

&lt;p&gt;命中率=返回正确结果数/请求缓存次数，命中率问题是缓存中的一个非常重要的问题，它是衡量缓存有效性的重要指标。命中率越高，表明缓存的使用率越高。&lt;/p&gt;

&lt;h4 id=&quot;最大元素或最大空间&quot;&gt;最大元素（或最大空间）&lt;/h4&gt;

&lt;p&gt;缓存中可以存放的最大元素的数量，一旦缓存中元素数量超过这个值（或者缓存数据所占空间超过其最大支持空间），那么将会触发缓存启动清空策略根据不同的场景合理的设置最大元素值往往可以一定程度上提高缓存的命中率，从而更有效的时候缓存。&lt;/p&gt;

&lt;h4 id=&quot;清空策略&quot;&gt;清空策略&lt;/h4&gt;

&lt;p&gt;如上描述，缓存的存储空间有限制，当缓存空间被用满时，如何保证在稳定服务的同时有效提升命中率？这就由缓存清空策略来处理，设计适合自身数据特征的清空策略能有效提升命中率。常见的一般策略有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;FIFO(first in first out)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;先进先出策略，最先进入缓存的数据在缓存空间不够的情况下（超出最大元素限制）会被优先被清除掉，以腾出新的空间接受新的数据。策略算法主要比较缓存元素的创建时间。在数据实效性要求场景下可选择该类策略，优先保障最新数据可用。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;LFU(less frequently used)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最少使用策略，无论是否过期，根据元素的被使用次数判断，清除使用次数较少的元素释放空间。策略算法主要比较元素的hitCount（命中次数）。在保证高频数据有效性场景下，可选择这类策略。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;LRU(least recently used)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最近最少使用策略，无论是否过期，根据元素最后一次被使用的时间戳，清除最远使用时间戳的元素释放空间。策略算法主要比较元素最近一次被get使用时间。在热点数据场景下较适用，优先保证热点数据的有效性。&lt;/p&gt;

&lt;h3 id=&quot;缓存的类型&quot;&gt;缓存的类型&lt;/h3&gt;

&lt;h4 id=&quot;1缓存存储介质&quot;&gt;1.缓存存储介质&lt;/h4&gt;

&lt;p&gt;虽然从硬件介质上来看，无非就是内存和硬盘两种，但从技术上，可以分成内存、硬盘文件、数据库。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;内存：&lt;/strong&gt;将缓存存储于内存中是最快的选择，无需额外的I/O开销，但是内存的缺点是没有持久化落地物理磁盘，一旦应用异常break down而重新启动，数据很难或者无法复原。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;硬盘：&lt;/strong&gt;一般来说，很多缓存框架会结合使用内存和硬盘，在内存分配空间满了或是在异常的情况下，可以被动或主动的将内存空间数据持久化到硬盘中，达到释放空间或备份数据的目的。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;数据库：&lt;/strong&gt;前面有提到，增加缓存的策略的目的之一就是为了减少数据库的I/O压力。现在使用数据库做缓存介质是不是又回到了老问题上了？其实，数据库也有很多种类型，像那些不支持SQL，只是简单的key-value存储结构的特殊数据库（如BerkeleyDB和Redis），响应速度和吞吐量都远远高于我们常用的关系型数据库等。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;2缓存的分类&quot;&gt;2.缓存的分类&lt;/h4&gt;

&lt;p&gt;根据缓存与应用的藕合度，分为local cache（本地缓存）和remote cache（分布式缓存）：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;本地缓存&lt;/strong&gt;：指的是在应用中的缓存组件，其最大的优点是应用和cache是在同一个进程内部，请求缓存非常快速，没有过多的网络开销等，在单应用不需要集群支持或者集群情况下各节点无需互相通知的场景下使用本地缓存较合适；同时，它的缺点也是应为缓存跟应用程序耦合，多个应用程序无法直接的共享缓存，各应用或集群的各节点都需要维护自己的单独缓存，对内存是一种浪费。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;分布式缓存&lt;/strong&gt;：指的是与应用分离的缓存组件或服务，其最大的优点是自身就是一个独立的应用，与本地应用隔离，多个应用可直接的共享缓存&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;本地缓存&quot;&gt;本地缓存&lt;/h3&gt;

&lt;h4 id=&quot;1编程直接实现缓存&quot;&gt;1.编程直接实现缓存&lt;/h4&gt;

&lt;p&gt;个别场景下，我们只需要简单的缓存数据的功能，而无需关注更多存取、清空策略等深入的特性时，直接编程实现缓存则是最便捷和高效的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a. 成员变量或局部变量实现&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;UseLocalCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
     &lt;span class=&quot;c1&quot;&gt;//一个本地的缓存变量&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;localCacheStoreMap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;infosList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInfoList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;item:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;infosList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;localCacheStoreMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)){&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//缓存命中 使用缓存数据&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// todo&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 缓存未命中  IO获取数据，结果存入缓存&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valueObject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInfoFromDB&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;localCacheStoreMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;valueObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valueObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//示例&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getInfoList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//示例数据库IO获取&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getInfoFromDB&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;以局部变量map结构缓存部分业务数据，减少频繁的重复数据库I/O操作。缺点仅限于类的自身作用域内，类间无法共享缓存。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;b. 静态变量实现&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;最常用的单例实现静态资源缓存，代码示例如下：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;      &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CityUtils&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HttpClient&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ServerHolder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createClientWithPool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; 
      &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cityIdNameMap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;();&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;districtIdNameMap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;();&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;HttpGet&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HttpGet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://gis-in.sankuai.com/api/location/city/all&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;BaseAuthorizationUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;generateAuthAndDateHeader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;BaseAuthorizationUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;CLIENT_TO_REQUEST_MDC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;BaseAuthorizationUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SECRET_TO_REQUEST_MDC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resultStr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BasicResponseHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;JSONObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resultJo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resultStr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;JSONArray&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataJa&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resultJo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getJSONArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataJa&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;JSONObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;itemJo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataJa&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getJSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;cityIdNameMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;itemJo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;itemJo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Init City List Error!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;HttpGet&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HttpGet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://gis-in.sankuai.com/api/location/district/all&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;BaseAuthorizationUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;generateAuthAndDateHeader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;BaseAuthorizationUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;CLIENT_TO_REQUEST_MDC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;BaseAuthorizationUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SECRET_TO_REQUEST_MDC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resultStr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BasicResponseHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;JSONObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resultJo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resultStr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;JSONArray&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataJa&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resultJo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getJSONArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataJa&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;JSONObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;itemJo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataJa&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getJSONObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;districtIdNameMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;itemJo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;itemJo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Init District List Error!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getCityName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cityId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cityIdNameMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cityId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;未知&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
     &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getDistrictName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;districtId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;districtIdNameMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;districtId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;未知&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
     &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;O2O业务中常用的城市基础基本信息判断，通过静态变量一次获取缓存内存中，减少频繁的I/O读取，静态变量实现类间可共享，进程内可共享，缓存的实时性稍差。&lt;/p&gt;

&lt;p&gt;为了解决本地缓存数据的实时性问题，目前大量使用的是结合ZooKeeper的自动发现机制，实时变更本地静态变量缓存：&lt;/p&gt;

&lt;p&gt;美团内部的基础配置组件MtConfig，采用的就是类似原理，使用静态变量缓存，结合ZooKeeper的统一管理，做到自动动态更新缓存，如图所示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/ef639c8f.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;直接编程的缓存实现，优点是能直接在heap区内读写，最快也最方便；缺点同样是受heap区域影响，缓存的数据量非常有限，同时缓存时间受GC影响。主要满足单机场景下的小数据量缓存需求，同时对缓存数据的变更无需太敏感感知，如上一般配置管理、基础静态数据等场景。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;2缓存框架ehcache&quot;&gt;2.缓存框架——Ehcache&lt;/h4&gt;

&lt;p&gt;Ehcache是现在最流行的纯Java开源缓存框架，配置简单、结构清晰、功能强大，是一个非常轻量级的缓存实现，我们常用的Hibernate里面就集成了相关缓存功能。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/b810d158.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;图3 Ehcache框架图&lt;/p&gt;

&lt;p&gt;从图3中我们可以了解到，Ehcache的核心定义主要包括：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;cache manager：&lt;/strong&gt;缓存管理器，以前是只允许单例的，不过现在也可以多实例了。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;cache：&lt;/strong&gt;缓存管理器内可以放置若干cache，存放数据的实质，所有cache都实现了Ehcache接口，这是一个真正使用的缓存实例；通过缓存管理器的模式，可以在单个应用中轻松隔离多个缓存实例，独立服务于不同业务场景需求，缓存数据物理隔离，同时需要时又可共享使用。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;element：&lt;/strong&gt;单条缓存数据的组成单位。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;system of record（SOR）：&lt;/strong&gt;可以取到真实数据的组件，可以是真正的业务逻辑、外部接口调用、存放真实数据的数据库等，缓存就是从SOR中读取或者写入到SOR中去的。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在上层可以看到，整个Ehcache提供了对JSR、JMX等的标准支持，能够较好的兼容和移植，同时对各类对象有较完善的监控管理机制。它的缓存介质涵盖堆内存（heap）、堆外内存（BigMemory商用版本支持）和磁盘，各介质可独立设置属性和策略。Ehcache最初是独立的本地缓存框架组件，在后期的发展中，结合Terracotta服务阵列模型，可以支持分布式缓存集群，主要有RMI、JGroups、JMS和Cache Server等传播方式进行节点间通信，如图3的左侧部分描述。&lt;/p&gt;

&lt;p&gt;整体数据流转包括这样几类行为:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Flush：缓存条目向低层次移动。&lt;/li&gt;
  &lt;li&gt;Fault：从低层拷贝一个对象到高层。在获取缓存的过程中，某一层发现自己的该缓存条目已经失效，就触发了Fault行为。&lt;/li&gt;
  &lt;li&gt;Eviction：把缓存条目除去。&lt;/li&gt;
  &lt;li&gt;Expiration：失效状态。&lt;/li&gt;
  &lt;li&gt;Pinning：强制缓存条目保持在某一层。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;图4反映了数据在各个层之间的流转，同时也体现了各层数据的一个生命周期。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/2cfea699.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;图4 缓存数据流转图（L1:本地内存层；L2:Terracotta服务节点层)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;主要特性：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;快速，针对大型高并发系统场景，Ehcache的多线程机制有相应的优化改善。&lt;/li&gt;
  &lt;li&gt;简单，很小的jar包，简单配置就可直接使用，单机场景下无需过多的其他服务依赖。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;支持多种的缓存策略，灵活。&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;缓存数据有两级&lt;/strong&gt;：内存和磁盘，与一般的本地内存缓存相比，&lt;strong&gt;有了磁盘的存储空间，将可以支持更大量的数据缓存需求。&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;具有缓存和缓存管理器的侦听接口，能更简单方便的进行缓存实例的监控管理。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;支持多缓存管理器实例，以及一个实例的多个缓存区域&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;整体上看，Ehcache的使用还是相对简单便捷的，提供了完整的各类API接口。需要注意的是，虽然Ehcache支持磁盘的持久化，但是由于存在两级缓存介质，在一级内存中的缓存，如果没有主动的刷入磁盘持久化的话，在应用异常down机等情形下，依然会出现缓存数据丢失，为此可以根据需要将缓存刷到磁盘，将缓存条目刷到磁盘的操作可以通过cache.flush()方法来执行，需要注意的是，对于对象的磁盘写入，前提是要将对象进行序列化。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;ehcache原理&quot;&gt;Ehcache原理&lt;/h3&gt;

&lt;p&gt;它的缓存介质涵盖堆内存（heap）、堆外内存；其中堆外内存的实现通过nio的DirectByteBuffer类；&lt;/p&gt;

&lt;p&gt;（1）系统IO调用&lt;/p&gt;

&lt;p&gt;首先来看一下一般的IO调用。在传统的文件IO操作中，我们都是调用操作系统提供的底层标准IO系统调用函数 read()、write() ，此时调用此函数的进程（在JAVA中即java进程）由当前的用户态切换到内核态，然后OS的内核代码负责将相应的文件数据读取到内核的IO缓冲区，然后再把数据从内核IO缓冲区拷贝到进程的私有地址空间中去，这样便完成了一次IO操作。如下图所示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://img-blog.csdn.net/20170607224313512?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGlueGRjbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;注意两点：&lt;/p&gt;

&lt;p&gt;OS的read函数会在内核IO缓冲区中预读取数据，减少磁盘IO操作（Step2）
Java的BufferedReader或BufferedInputStream的缓冲区的作用是减少系统调用（Step1）
（2）内存映射文件&lt;/p&gt;

&lt;p&gt;内存文件映射适用于对大文件的读写。虚拟地址空间有一块区域： “Memory mapped region for shared libraries” ，这段区域就是在内存映射文件的时候将某一段的虚拟地址和文件对象的某一部分建立起映射关系，此时并没有拷贝数据到内存中去，而是当进程代码第一次引用这段代码内的虚拟地址时，触发了缺页异常，这时候OS根据映射关系直接将文件的相关部分数据拷贝到进程的用户私有空间中去，如下图所示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://img-blog.csdn.net/20170607225947116?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGlueGRjbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;内存映射文件的效率比标准IO高的重要原因就是因为少了把数据拷贝到OS内核缓冲区这一步。&lt;/p&gt;

&lt;p&gt;MappedByteBuffer：映射是通过FileChannel提供的map方法把文件映射到虚拟内存，通常情况可以映射整个文件，如果文件比较大，可以进行分段映射。&lt;/p&gt;

&lt;p&gt;DirectByteBuffer：继承了MappedByteBuffer，主要是实现了byte获得函数get等&lt;/p&gt;

&lt;h3 id=&quot;ehcache使用&quot;&gt;Ehcache使用&lt;/h3&gt;

&lt;h4 id=&quot;1ehcache基本操作&quot;&gt;1.Ehcache基本操作&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Element&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;Cache&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;CacheManager&lt;/code&gt;是Ehcache最重要的API。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Element：缓存的元素，它维护着一个键值对。&lt;/li&gt;
  &lt;li&gt;Cache：它是Ehcache的核心类，它有多个&lt;code class=&quot;highlighter-rouge&quot;&gt;Element&lt;/code&gt;，并被&lt;code class=&quot;highlighter-rouge&quot;&gt;CacheManager&lt;/code&gt;管理。它实现了对缓存的逻辑行为。&lt;/li&gt;
  &lt;li&gt;CacheManager：&lt;code class=&quot;highlighter-rouge&quot;&gt;Cache&lt;/code&gt;的容器对象，并管理着&lt;code class=&quot;highlighter-rouge&quot;&gt;Cache&lt;/code&gt;的生命周期。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;创建cachemanager&quot;&gt;创建CacheManager&lt;/h5&gt;

&lt;p&gt;下面的代码列举了创建&lt;code class=&quot;highlighter-rouge&quot;&gt;CacheManager&lt;/code&gt;的五种方式。 使用静态方法&lt;code class=&quot;highlighter-rouge&quot;&gt;create()&lt;/code&gt;会以默认配置来创建单例的&lt;code class=&quot;highlighter-rouge&quot;&gt;CacheManager&lt;/code&gt;实例。&lt;code class=&quot;highlighter-rouge&quot;&gt;newInstance()&lt;/code&gt;方法是一个工厂方法，以默认配置创建一个新的&lt;code class=&quot;highlighter-rouge&quot;&gt;CacheManager&lt;/code&gt;实例。 此外，&lt;code class=&quot;highlighter-rouge&quot;&gt;newInstance()&lt;/code&gt;还有几个重载函数，分别可以通过传入&lt;code class=&quot;highlighter-rouge&quot;&gt;String&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;URL&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;InputStream&lt;/code&gt;参数来加载配置文件，然后创建&lt;code class=&quot;highlighter-rouge&quot;&gt;CacheManager&lt;/code&gt;实例。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 使用Ehcache默认配置获取单例的CacheManager实例&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cacheNames&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCacheNames&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 使用Ehcache默认配置新建一个CacheManager实例&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cacheNames&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCacheNames&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 使用不同的配置文件分别创建一个CacheManager实例&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;src/config/ehcache1.xml&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;src/config/ehcache2.xml&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cacheNamesForManager1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCacheNames&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cacheNamesForManager2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCacheNames&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 基于classpath下的配置文件创建CacheManager实例&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;URL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/anotherconfigurationname.xml&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 基于文件流得到配置文件，并创建CacheManager实例&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;InputStream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fis&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FileInputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;File&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;src/config/ehcache.xml&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAbsolutePath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;fis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2添加缓存&quot;&gt;2.添加缓存&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;需要强调一点，Cache对象在用addCache方法添加到CacheManager之前，是无效的。&lt;/em&gt;&lt;/strong&gt; 使用CacheManager的addCache方法可以根据缓存名将ehcache.xml中声明的cache添加到容器中；它也可以直接将Cache对象添加到缓存容器中。 &lt;code class=&quot;highlighter-rouge&quot;&gt;Cache&lt;/code&gt;有多个构造函数，提供了不同方式去加载缓存的配置参数。 有时候，你可能需要使用API来动态的添加缓存，下面的例子就提供了这样的范例。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 除了可以使用xml文件中配置的缓存，你也可以使用API动态增删缓存&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 添加缓存&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 使用默认配置添加缓存&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;singletonManager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;singletonManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;testCache&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;singletonManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;testCache&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 使用自定义配置添加缓存，注意缓存未添加进CacheManager之前并不可用&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;singletonManager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memoryOnlyCache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;testCache&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;singletonManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;memoryOnlyCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;singletonManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;testCache&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 使用特定的配置添加缓存&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;testCache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CacheConfiguration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;testCache&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxEntriesLocalHeap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;memoryStoreEvictionPolicy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MemoryStoreEvictionPolicy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;LFU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;eternal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timeToLiveSeconds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timeToIdleSeconds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;diskExpiryThreadIntervalSeconds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;persistence&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PersistenceConfiguration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Strategy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;LOCALTEMPSWAP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)));&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;testCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;3删除缓存&quot;&gt;3.删除缓存&lt;/h4&gt;

&lt;p&gt;删除缓存比较简单，你只需要将指定的缓存名传入&lt;code class=&quot;highlighter-rouge&quot;&gt;removeCache&lt;/code&gt;方法即可。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;CacheManager singletonManager = CacheManager.create();
singletonManager.removeCache(&quot;sampleCache1&quot;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;4基本缓存操作&quot;&gt;4.基本缓存操作&lt;/h4&gt;

&lt;p&gt;Cache最重要的两个方法就是put和get，分别用来添加Element和获取Element。 Cache还提供了一系列的get、set方法来设置或获取缓存参数，这里不一一列举，更多API操作可参考&lt;a href=&quot;http://www.ehcache.org/generated/2.10.2/pdf/Ehcache_API_Developer_Guide.pdf&quot;&gt;官方API开发手册&lt;/a&gt;。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * 测试：使用默认配置或使用指定配置来创建CacheManager
 *
 * @author Zhang Peng
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CacheOperationTest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LoggerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CacheOperationTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 使用Ehcache默认配置(classpath下的ehcache.xml)获取单例的CacheManager实例
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;operation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;src/test/resources/ehcache/ehcache.xml&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 获得Cache的引用&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;userCache&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 将一个Element添加到Cache&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;value1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 获取Element，Element类支持序列化，所以下面两种方法都可以用&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 获取非序列化的值&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key:{}, value:{}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getObjectKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getObjectValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 获取序列化的值&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key:{}, value:{}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 更新Cache中的Element&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;value2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key:{}, value:{}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getObjectKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getObjectValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 获取Cache的元素数&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cache size:{}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 获取MemoryStore的元素数&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MemoryStoreSize:{}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getMemoryStoreSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 获取DiskStore的元素数&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;DiskStoreSize:{}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDiskStoreSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 移除Element&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cache size:{}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 关闭当前CacheManager对象&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;shutdown&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 关闭CacheManager单例实例&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;CacheManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;shutdown&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;5-spring整合ehcache&quot;&gt;5. Spring整合Ehcache&lt;/h4&gt;

&lt;p&gt;Spring3.1开始添加了对缓存的支持。和事务功能的支持方式类似，&lt;strong&gt;缓存抽象允许底层使用不同的缓存解决方案来进行整合。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt;Spring对缓存的支持类似于对事务的支持。 首先使用注解标记方法，相当于定义了切点，然后使用Aop技术在这个方法的调用前、调用后获取方法的入参和返回值，进而实现了缓存的逻辑。而Spring Cache利用了Spring AOP的动态代理技术，即当客户端尝试调用pojo的foo()方法的时候，给它的不是pojo自身的引用，而是一个动态生成的代理类。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/fceabe48.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;一般首先要在spring中加入对ehcache的管理：&lt;/p&gt;

    &lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;bean&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ehcache&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;org.springframework.cache.ehcache.EhCacheManagerFactoryBean&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;configLocation&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;classpath:ehcache/ehcache.xml&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/bean&amp;gt;&lt;/span&gt;
   
&lt;span class=&quot;nt&quot;&gt;&amp;lt;bean&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cacheManager&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;org.springframework.cache.ehcache.EhCacheCacheManager&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cacheManager&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ref=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ehcache&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/bean&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;注解开启spring对ehcache的支持；&lt;/p&gt;

    &lt;p&gt;(1) 在xml中声明 像上一节spring-ehcache.xml中的做法一样，使用&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;cache:annotation-driven/&amp;gt;&lt;/code&gt;&lt;/p&gt;

    &lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;cache:annotation-driven&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cache-manager=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cacheManager&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;(2) 使用标记注解 你也可以通过对一个类进行注解修饰的方式在这个类中使用缓存注解。 范例如下：&lt;/p&gt;

    &lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableCaching&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AppConfig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;通过配置，生成cacheManager/cache（见上面1-3）&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;使用注解操作缓存： 下面前三个注解都是方法级别：&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;@Cacheable&lt;/strong&gt;&lt;/p&gt;

        &lt;p&gt;表明所修饰的方法是可以缓存的：当第一次调用这个方法时，它的结果会被缓存下来，在缓存的有效时间内，以后访问这个方法都直接返回缓存结果，不再执行方法中的代码段。 这个注解可以用&lt;code class=&quot;highlighter-rouge&quot;&gt;condition&lt;/code&gt;属性来设置条件，如果不满足条件，就不使用缓存能力，直接执行方法。 可以使用&lt;code class=&quot;highlighter-rouge&quot;&gt;key&lt;/code&gt;属性来指定key的生成规则。&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;@CachePut&lt;/strong&gt;&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;与&lt;code class=&quot;highlighter-rouge&quot;&gt;@Cacheable&lt;/code&gt;不同，&lt;code class=&quot;highlighter-rouge&quot;&gt;@CachePut&lt;/code&gt;不仅会缓存方法的结果，还会执行方法的代码段。 它支持的属性和用法都与&lt;code class=&quot;highlighter-rouge&quot;&gt;@Cacheable&lt;/code&gt;一致。&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;@CacheEvict&lt;/strong&gt;&lt;/p&gt;

        &lt;p&gt;与&lt;code class=&quot;highlighter-rouge&quot;&gt;@Cacheable&lt;/code&gt;功能相反，&lt;code class=&quot;highlighter-rouge&quot;&gt;@CacheEvict&lt;/code&gt;表明所修饰的方法是用来删除失效或无用的缓存数据。&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;@Caching&lt;/strong&gt;&lt;/p&gt;

        &lt;p&gt;如果需要使用同一个缓存注解（&lt;code class=&quot;highlighter-rouge&quot;&gt;@Cacheable&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;@CacheEvict&lt;/code&gt;或&lt;code class=&quot;highlighter-rouge&quot;&gt;@CachePut&lt;/code&gt;）多次修饰一个方法，就需要用到&lt;code class=&quot;highlighter-rouge&quot;&gt;@Caching&lt;/code&gt;。&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;

    &lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Caching&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;evict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@CacheEvict&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;  	     &lt;span class=&quot;nd&quot;&gt;@CacheEvict&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheNames&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;secondary&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#p0&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Book&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;importBooks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deposit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Date&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;@CacheConfig&lt;/strong&gt;&lt;/p&gt;

        &lt;p&gt;&lt;strong&gt;与前面的缓存注解不同，这是一个类级别的注解。&lt;/strong&gt; 如果类的所有操作都是缓存操作，你可以使用&lt;code class=&quot;highlighter-rouge&quot;&gt;@CacheConfig&lt;/code&gt;来指定类，省去一些配置。&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;

    &lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@CacheConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;books&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BookRepositoryImpl&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BookRepository&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;nd&quot;&gt;@Cacheable&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Book&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;findBook&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ISBN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isbn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{...}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;标签类型&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;作用&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;主要配置参数说明&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;@Cacheable&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;主要针对方法配置，能够根据方法的请求参数对其结果进行缓存&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;value：&lt;/strong&gt;缓存的名称，在 Spring 配置文件中定义，必须指定至少一个； &lt;strong&gt;key：&lt;/strong&gt;缓存的 key，可以为空，如果指定要按照 SpEL 表达式编写，如果不指定，则默认按照方法的所有参数进行组合； &lt;strong&gt;condition：&lt;/strong&gt;缓存的条件，可以为空，使用 SpEL 编写，返回 true 或者 false，只有为 true 才进行缓存&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;@CachePut&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;主要针对方法配置，能够根据方法的请求参数对其结果进行缓存，和 @Cacheable 不同的是，它每次都会触发真实方法的调用&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;value：&lt;/strong&gt;缓存的名称，在 spring 配置文件中定义，必须指定至少一个; &lt;strong&gt;key：&lt;/strong&gt;缓存的 key，可以为空，如果指定要按照 SpEL 表达式编写，如果不指定，则默认按照方法的所有参数进行组合； &lt;strong&gt;condition：&lt;/strong&gt;缓存的条件，可以为空，使用 SpEL 编写，返回 true 或者 false，只有为 true 才进行缓存&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;@CacheEvict&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;主要针对方法配置，能够根据一定的条件对缓存进行清空&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;value：&lt;/strong&gt;缓存的名称，在 Spring 配置文件中定义，必须指定至少一个； &lt;strong&gt;key：&lt;/strong&gt;缓存的 key，可以为空，如果指定要按照 SpEL 表达式编写，如果不指定，则默认按照方法的所有参数进行组合； &lt;strong&gt;condition：&lt;/strong&gt;缓存的条件，可以为空，使用 SpEL 编写，返回 true 或者 false，只有为 true 才进行缓存； &lt;strong&gt;allEntries：&lt;/strong&gt;是否清空所有缓存内容，默认为 false，如果指定为 true，则方法调用后将立即清空所有缓存； &lt;strong&gt;beforeInvocation：&lt;/strong&gt;是否在方法执行前就清空，默认为 false，如果指定为 true，则在方法还没有执行的时候就清空缓存，默认情况下，如果方法执行抛出异常，则不会清空缓存&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;redis缓存&quot;&gt;Redis缓存&lt;/h3&gt;

&lt;h4 id=&quot;redis存储原理&quot;&gt;Redis存储原理&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;1.RedisObject&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Redis内部使用一个redisObject对象来标识所有的key和value数据，redisObject最主要的信息如图所示：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;type代表一个value对象具体是何种数据类型，&lt;/li&gt;
  &lt;li&gt;encoding是不同数据类型在Redis内部的存储方式，比如：type=string代表value存储的是一个普通字符串，那么对应的encoding可以是raw或是int，如果是int则代表世界Redis内部是按数值类型存储和表示这个字符串。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/58db8aae.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;图9左边的raw列为对象的编码方式：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;字符串可以被编码为raw（一般字符串）或Rint（为了节约内存，Redis会将字符串表示的64位有符号整数编码为整数来进行储存）；&lt;/li&gt;
  &lt;li&gt;列表可以被编码为ziplist或linkedlist，ziplist是为节约大小较小的列表空间而作的特殊表示；&lt;/li&gt;
  &lt;li&gt;集合可以被编码为intset或者hashtable，intset是只储存数字的小集合的特殊表示；&lt;/li&gt;
  &lt;li&gt;hash表可以编码为zipmap或者hashtable，zipmap是小hash表的特殊表示；&lt;/li&gt;
  &lt;li&gt;有序集合可以被编码为ziplist或者skiplist格式，ziplist用于表示小的有序集合，而skiplist则用于表示任何大小的有序集合；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;IO模型：&lt;/strong&gt;从网络I/O模型上看，Redis使用单线程的I/O复用模型，自己封装了一个简单的AeEvent事件处理框架，主要实现了epoll、kqueue和select。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;内存申请：&lt;/strong&gt;相较于memcached的预分配内存管理，Redis使用现场申请内存的方式来存储数据，并且很少使用free-list等方式来优化内存分配，会在一定程度上存在内存碎片。Redis跟据存储命令参数，会把带过期时间的数据单独存放在一起，并把它们称为临时数据，非临时数据是永远不会被剔除的，即便物理内存不够，导致swap也不会剔除任何非临时数据（但会尝试剔除部分临时数据）。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.持久化方式&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;我们描述Redis为内存数据库，作为缓存服务，大量使用内存间的数据快速读写，支持高并发大吞吐；而作为数据库，则是指Redis对缓存的持久化支持。Redis由于支持了非常丰富的内存数据库结构类型，如何把这些复杂的内存组织方式持久化到磁盘上？Redis的持久化与传统数据库的方式差异较大，&lt;strong&gt;Redis一共支持四种持久化方式，主要使用的两种：&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;定时快照方式(snapshot)：&lt;/strong&gt;该持久化方式实际是在Redis内部一个定时器事件，每隔固定时间去检查当前数据发生的改变次数与时间是否满足配置的持久化触发的条件，如果满足则通过操作系统fork调用来创建出一个子进程，这个子进程默认会与父进程共享相同的地址空间，这时就可以通过子进程来遍历整个内存来进行存储操作，而主进程则仍然可以提供服务，当有写入时由操作系统按照内存页（page）为单位来进行copy-on-write保证父子进程之间不会互相影响。它的缺点是快照只是代表一段时间内的内存映像，所以系统重启会丢失上次快照与重启之间所有的数据。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;基于语句追加文件的方式(aof)：&lt;/strong&gt;aof方式实际类似MySQl的基于语句的binlog方式，即每条会使Redis内存数据发生改变的命令都会追加到一个log文件中，也就是说这个log文件就是Redis的持久化数据。&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;aof的方式的主要缺点是&lt;strong&gt;追加log文件可能导致体积过大&lt;/strong&gt;，当系统重启恢复数据时如果是aof的方式则加载数据会非常慢，几十G的数据可能需要几小时才能加载完，当然这个耗时并不是因为磁盘文件读取速度慢，而是由于读取的所有命令都要在内存中执行一遍。另外由于每条命令都要写log，所以使用aof的方式，Redis的读写性能也会有所下降。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Redis的持久化使用了Buffer I/O&lt;/strong&gt;，所谓Buffer I/O是指Redis对持久化文件的写入和读取操作都会使用物理内存的Page Cache，而大多数数据库系统会使用Direct I/O来绕过这层Page Cache并自行维护一个数据的Cache。而当Redis的持久化文件过大（尤其是快照文件），并对其进行读写时，磁盘文件中的数据都会被加载到物理内存中作为操作系统对该文件的一层Cache，而这层Cache的数据与Redis内存中管理的数据实际是重复存储的。虽然内核在物理内存紧张时会做Page Cache的剔除工作，但内核很可能认为某块Page Cache更重要，而让你的进程开始Swap，这时你的系统就会开始出现不稳定或者崩溃了，因此在持久化配置后，针对内存使用需要实时监控观察。&lt;/p&gt;

&lt;h4 id=&quot;分布式redis缓存&quot;&gt;分布式Redis缓存&lt;/h4&gt;

&lt;p&gt;Redis更倾向于在服务端构建分布式存储&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/0941f7e1.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/ce564c30.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;通过集群+主从结合的设计，Redis在扩展和稳定高可用性能方面都是比较成熟的。&lt;/p&gt;

&lt;p&gt;但是，&lt;strong&gt;在数据一致性问题上，Redis没有提供CAS操作命令来保障高并发场景下的数据一致性问题&lt;/strong&gt;，不过它却&lt;strong&gt;提供了事务的功能&lt;/strong&gt;，Redis的Transactions提供的并不是严格的ACID的事务（比如一串用EXEC提交执行的命令，在执行中服务器宕机，那么会有一部分命令执行了，剩下的没执行）。但是这个Transactions还是提供了基本的命令打包执行的功能（在服务器不出问题的情况下，可以保证一连串的命令是顺序在一起执行的，中间有会有其它客户端命令插进来执行）&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;以下多种Web应用场景，在这些场景下可以充分的利用Redis的特性，大大提高效率。&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在主页中显示最新的项目列表：Redis使用的是常驻内存的缓存，速度非常快。LPUSH用来插入一个内容ID，作为关键字存储在列表头部。LTRIM用来限制列表中的项目数最多为5000。如果用户需要的检索的数据量超越这个缓存容量，这时才需要把请求发送到数据库。&lt;/li&gt;
  &lt;li&gt;删除和过滤：如果一篇文章被删除，可以使用LREM从缓存中彻底清除掉。&lt;/li&gt;
  &lt;li&gt;排行榜及相关问题：排行榜（leader board）按照得分进行排序。ZADD命令可以直接实现这个功能，而ZREVRANGE命令可以用来按照得分来获取前100名的用户，ZRANK可以用来获取用户排名，非常直接而且操作容易。&lt;/li&gt;
  &lt;li&gt;按照用户投票和时间排序：排行榜，得分会随着时间变化。LPUSH和LTRIM命令结合运用，把文章添加到一个列表中。一项后台任务用来获取列表，并重新计算列表的排序，ZADD命令用来按照新的顺序填充生成列表。列表可以实现非常快速的检索，即使是负载很重的站点。&lt;/li&gt;
  &lt;li&gt;过期项目处理：使用Unix时间作为关键字，用来保持列表能够按时间排序。对current_time和time_to_live进行检索，完成查找过期项目的艰巨任务。另一项后台任务使用ZRANGE…WITHSCORES进行查询，删除过期的条目。&lt;/li&gt;
  &lt;li&gt;计数：进行各种数据统计的用途是非常广泛的，比如想知道什么时候封锁一个IP地址。INCRBY命令让这些变得很容易，通过原子递增保持计数；GETSET用来重置计数器；过期属性用来确认一个关键字什么时候应该删除。&lt;/li&gt;
  &lt;li&gt;特定时间内的特定项目：这是特定访问者的问题，可以通过给每次页面浏览使用SADD命令来解决。SADD不会将已经存在的成员添加到一个集合。&lt;/li&gt;
  &lt;li&gt;Pub/Sub：在更新中保持用户对数据的映射是系统中的一个普遍任务。Redis的pub/sub功能使用了SUBSCRIBE、UNSUBSCRIBE和PUBLISH命令，让这个变得更加容易。&lt;/li&gt;
  &lt;li&gt;队列：在当前的编程中队列随处可见。除了push和pop类型的命令之外，Redis还有阻塞队列的命令，能够让一个程序在执行时被另一个程序添加到队列。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;阿里分布式缓存服务-tair&quot;&gt;阿里分布式缓存服务 Tair&lt;/h3&gt;

&lt;p&gt;一个 Tair 集群主要包括 3 个必选模块：configserver、dataserver和client，以及一个可选模块：invalidserver。通常情况下，一个集群中包含 2 台 configserver 及多台 dataServer 。两台 configserver 互为主备并通过维护和 dataserver 之间的心跳获知集群中存活可用的 dataserver ，构建数据在集群中的分布信息（对照表）。dataserver 负责数据的存储，并按照 configserver 的指示完成数据的复制和迁移工作。client 在启动的时候，从 configserver 获取数据分布信息，根据数据分布信息和相应的 dataserver 交互完成用户的请求。invalidserver 主要负责对等集群的删除和隐藏操作，保证对等集群的数据一致。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.oschina.net/uploads/img/201009/09100337_hWE9.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ConfigServer&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;通过维护和 dataserver 心跳来获知集群中存活节点的信息&lt;/li&gt;
  &lt;li&gt;根据存活节点的信息来构建数据在集群中的分布表&lt;/li&gt;
  &lt;li&gt;提供数据分布表的查询服务&lt;/li&gt;
  &lt;li&gt;调度 dataserver 之间的数据迁移、复制&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;DataServer&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;提供存储引擎&lt;/li&gt;
  &lt;li&gt;接受 client 的 put/get/remove 等操作&lt;/li&gt;
  &lt;li&gt;执行数据迁移，复制等&lt;/li&gt;
  &lt;li&gt;插件：在接受请求的时候处理一些自定义功能&lt;/li&gt;
  &lt;li&gt;访问统计&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;client&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;在应用端提供访问 Tair 集群的接口&lt;/li&gt;
  &lt;li&gt;更新并缓存数据分布表和 invalidserver 地址等&lt;/li&gt;
  &lt;li&gt;LocalCache，避免过热数据访问影响 Tair 集群服务&lt;/li&gt;
  &lt;li&gt;流控&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;InvalidServer&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;接收来自 client 的 invalid/hide 等请求后，对属于同一组的集群（双机房独立集群部署方式）做delete/hide操作，保证同一组集群的一致&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;集群断网之后的，脏数据清理&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;访问统计&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;http://www.oschina.net/uploads/img/201009/09100338_TX6x.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;参考&quot;&gt;参考&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://tech.meituan.com/2017/03/17/cache-about.html&quot;&gt;美团-缓存&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;&amp;lt;https://github.com/dunwu/spring-notes/blob/master/docs/spring/integration/spring-and-cache.md&quot;&gt;spring-notes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://raychase.iteye.com/blog/1545906&quot;&gt;https://raychase.iteye.com/blog/1545906&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 10 Mar 2019 00:00:00 +0000</pubDate>
        <link>http://offery-gong.github.io/2019/03/10/%E7%BC%93%E5%AD%98/</link>
        <guid isPermaLink="true">http://offery-gong.github.io/2019/03/10/%E7%BC%93%E5%AD%98/</guid>
        
        <category>分布式技术</category>
        
        
      </item>
    
      <item>
        <title>SpringBoot定时任务注解：@Scheduled源码解析</title>
        <description>&lt;h3 id=&quot;1-scheduled&quot;&gt;1. @Scheduled&lt;/h3&gt;

&lt;p&gt;先看一下@Scheduled源码&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;springframework&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;scheduling&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;annotation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;METHOD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ANNOTATION_TYPE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Documented&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Repeatable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Schedules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Scheduled&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cron&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;zone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fixedDelay&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fixedDelayString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fixedRate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fixedRateString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialDelay&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialDelayString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;支持cron表达式（”cron” expression）、固定频率（fixedRate）、固定延时（fixedDelay） 3种调度方式。&lt;/p&gt;

&lt;p&gt;initial-delay : 表示第一次运行前需要延迟的时间，单位是毫秒&lt;br /&gt;
fixed-delay : 表示从上一个任务完成到下一个任务开始的间隔, 单位是毫秒。&lt;br /&gt;
fixed-rate : 表示从上一个任务开始到下一个任务开始的间隔, 单位是毫秒。(如果上一个任务执行超时，则可能是上一个任务执行完成后立即启动下一个任务)
cron : cron 表达式。(定时执行，如果上一次任务执行超时而导致某个定时间隔不能执行，则会顺延下一个定时间隔时间。下一个任务和上一个任务的间隔时间不固定)
区别见图&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ws2.sinaimg.cn/large/d8b81fbfly1g190j7h6o1j20l40gower.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-scheduledannotationbeanpostprocessor&quot;&gt;2. ScheduledAnnotationBeanPostProcessor&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ScheduledAnnotationBeanPostProcesso&lt;/code&gt;r是&lt;a href=&quot;https://github.com/scheduled&quot;&gt;@scheduled&lt;/a&gt;注解处理类，实现了&lt;code class=&quot;highlighter-rouge&quot;&gt;BeanPostProcessor&lt;/code&gt;接口（&lt;code class=&quot;highlighter-rouge&quot;&gt;postProcessAfterInitialization&lt;/code&gt;方法实现注解扫描和类实例创建）、&lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationContextAware&lt;/code&gt;接口（&lt;code class=&quot;highlighter-rouge&quot;&gt;setApplicationContext&lt;/code&gt;方法设置当前&lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationContext&lt;/code&gt;）、&lt;code class=&quot;highlighter-rouge&quot;&gt;org.springframework.context. ApplicationListener&lt;/code&gt;（观察者模式，&lt;code class=&quot;highlighter-rouge&quot;&gt;onApplicationEvent&lt;/code&gt;方法会被回调）,&lt;code class=&quot;highlighter-rouge&quot;&gt;DisposableBean&lt;/code&gt;接口（destroy方法中进行资源销毁操作）。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ScheduledAnnotationBeanPostProcessor&lt;/code&gt;中 &lt;code class=&quot;highlighter-rouge&quot;&gt;postProcessAfterInitialization()&lt;/code&gt;扫描所有&lt;a href=&quot;https://github.com/scheduled&quot;&gt;@scheduled&lt;/a&gt;注解，区分&lt;code class=&quot;highlighter-rouge&quot;&gt;cronTasks&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;fixedDelayTasks&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;fixedRateTasks&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;springframework&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;scheduling&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;annotation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ScheduledAnnotationBeanPostProcessor&lt;/span&gt;
		&lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MergedBeanDefinitionPostProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Destruc&lt;/span&gt;	&lt;span class=&quot;n&quot;&gt;tionAwareBeanPostProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;Ordered&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EmbeddedValueResolverAware&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BeanNameAware&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BeanFactoryAware&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ApplicationContextAware&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;SmartInitializingSingleton&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ApplicationListener&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ContextRefreshedEvent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DisposableBean&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;3-总体步骤&quot;&gt;3. 总体步骤&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;通过&lt;code class=&quot;highlighter-rouge&quot;&gt;@EnableScheduling&lt;/code&gt;注解，创建&lt;code class=&quot;highlighter-rouge&quot;&gt;ScheduledAnnotationBeanPostProcessor&lt;/code&gt;类实例；&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;@EnableScheduling&lt;/code&gt; 注解定义，如下：&lt;/p&gt;

    &lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;springframework&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;scheduling&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;annotation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
   
&lt;span class=&quot;nd&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Import&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SchedulingConfiguration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Documented&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EnableScheduling&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;Spring Boot项目中 &lt;code class=&quot;highlighter-rouge&quot;&gt;@EnableScheduling&lt;/code&gt;的魔法就在于 &lt;code class=&quot;highlighter-rouge&quot;&gt;@Import(SchedulingConfiguration.class)&lt;/code&gt;，看一下 &lt;code class=&quot;highlighter-rouge&quot;&gt;SchedulingConfiguration&lt;/code&gt;源码：创建了&lt;code class=&quot;highlighter-rouge&quot;&gt;ScheduledAnnotationBeanPostProcessor&lt;/code&gt;实例&lt;/p&gt;

    &lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Role&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BeanDefinition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ROLE_INFRASTRUCTURE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SchedulingConfiguration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   
	&lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TaskManagementConfigUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;nd&quot;&gt;@Role&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BeanDefinition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ROLE_INFRASTRUCTURE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ScheduledAnnotationBeanPostProcessor&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;scheduledAnnotationProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ScheduledAnnotationBeanPostProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
   
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ScheduledAnnotationBeanPostProcessor&lt;/code&gt; 实现&lt;code class=&quot;highlighter-rouge&quot;&gt;BeanPostProcessor&lt;/code&gt;接口（&lt;code class=&quot;highlighter-rouge&quot;&gt;postProcessAfterInitialization&lt;/code&gt;方法实现@Scheduled注解扫描和类实例创建）;&lt;code class=&quot;highlighter-rouge&quot;&gt;postProcessAfterInitialization()&lt;/code&gt;调用&lt;code class=&quot;highlighter-rouge&quot;&gt;processScheduled(Scheduled scheduled, Method method, Object bean)&lt;/code&gt; 实现了对注解&lt;code class=&quot;highlighter-rouge&quot;&gt;@Scheduled&lt;/code&gt; 的内容的解析，并将对应的调度任务类型添加到&lt;code class=&quot;highlighter-rouge&quot;&gt;ScheduledTaskRegistrar&lt;/code&gt; 实例中；最后再集中将这些task放入一个regTasks中。【这个regtask在哪里被消费了？】&lt;/p&gt;

    &lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;registrar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;scheduleFixedRateTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FixedRateTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;runnable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fixedRate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initialDelay&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)));&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Finally register the scheduled tasks&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;scheduledTasks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ScheduledTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;regTasks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;scheduledTasks&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;computeIfAbsent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LinkedHashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;regTasks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;spring 启动时，&lt;code class=&quot;highlighter-rouge&quot;&gt;AbstractApplicationContext&lt;/code&gt;中的&lt;code class=&quot;highlighter-rouge&quot;&gt;finishRefresh&lt;/code&gt;方法触发所有监视者方法回调：&lt;code class=&quot;highlighter-rouge&quot;&gt;ScheduledAnnotationBeanPostProcessor&lt;/code&gt;类所实现的&lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationListener&lt;/code&gt;类中的&lt;code class=&quot;highlighter-rouge&quot;&gt;onApplicationEvent()&lt;/code&gt;方法。&lt;code class=&quot;highlighter-rouge&quot;&gt;onApplicationEvent()&lt;/code&gt;调用&lt;code class=&quot;highlighter-rouge&quot;&gt;finishRegistration()&lt;/code&gt;方法完成&lt;code class=&quot;highlighter-rouge&quot;&gt;TaskScheduler&lt;/code&gt;的初始化（最终调用的是&lt;code class=&quot;highlighter-rouge&quot;&gt;ConcurrentTaskScheduler&lt;/code&gt;）；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;默认的 &lt;code class=&quot;highlighter-rouge&quot;&gt;ConcurrentTaskScheduler&lt;/code&gt; 计划执行器采用&lt;code class=&quot;highlighter-rouge&quot;&gt;Executors.newSingleThreadScheduledExecutor()&lt;/code&gt; 实现单线程的执行器。因此，对同一个调度任务的执行总是同一个线程。源码如下&lt;/p&gt;

    &lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ScheduledExecutorService&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;newSingleThreadScheduledExecutor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;DelegatedScheduledExecutorService&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ScheduledThreadPoolExecutor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;&lt;img src=&quot;http://wx1.sinaimg.cn/large/d8b81fbfly1g190t9iaulj20bs08maa0.jpg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;如果任务的执行时间超过该任务的下一次执行时间，则会出现任务丢失，&lt;/strong&gt;跳过该段时间的任务。上述问题有以下解决办法：&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;采用异步的方式执行调度任务，配置 Spring 的 &lt;code class=&quot;highlighter-rouge&quot;&gt;@EnableAsync&lt;/code&gt;，在任务执行的方法上标注 &lt;code class=&quot;highlighter-rouge&quot;&gt;@Async&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;配置任务执行池，采用 &lt;code class=&quot;highlighter-rouge&quot;&gt;ThreadPoolTaskScheduler.setPoolSize(n)&lt;/code&gt;。 &lt;code class=&quot;highlighter-rouge&quot;&gt;n&lt;/code&gt; 的数量为 &lt;code class=&quot;highlighter-rouge&quot;&gt;单个任务执行所需时间 / 任务执行的间隔时间&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;问题：&lt;/p&gt;

&lt;p&gt;创建了&lt;code class=&quot;highlighter-rouge&quot;&gt;ConcurrentTaskScheduler&lt;/code&gt; 来执行tasks。但是如何将前面的regTasks和这里的executor联系起来的呢？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww3.sinaimg.cn/large/006tNc79ly1g5w3x4nbx0j30pe0ciq6g.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;参考文献&quot;&gt;参考文献&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://tramp.cincout.cn/2017/08/18/spring-task-2017-08-18-spring-boot-enablescheduling-analysis/#ScheduledAnnotationBeanPostProcessor&quot;&gt;Spring @EnableScheduling 注解解析&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/TFdream/blog/issues/27&quot;&gt;Spring @Scheduled执行原理解析&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 08 Mar 2019 00:00:00 +0000</pubDate>
        <link>http://offery-gong.github.io/2019/03/08/SpringBoot%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E6%B3%A8%E8%A7%A3-@Scheduled%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/</link>
        <guid isPermaLink="true">http://offery-gong.github.io/2019/03/08/SpringBoot%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E6%B3%A8%E8%A7%A3-@Scheduled%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/</guid>
        
        <category>spring</category>
        
        
      </item>
    
      <item>
        <title>RPC &amp; RESTful</title>
        <description>&lt;h3 id=&quot;什么是rpc&quot;&gt;什么是RPC&lt;/h3&gt;

&lt;p&gt;下图是客户端调用远端服务的过程：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://tva1.sinaimg.cn/large/006y8mN6ly1g69dny3jh5j30ej092jsj.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;1、客户端client发起服务调用请求。
2、client stub 可以理解成一个代理，会将调用方法、参数按照一定格式进行封装，通过服务提供的地址，发起网络请求。
 3、消息通过网络传输到服务端。
 4、server stub接受来自socket的消息
 5、server stub将消息进行解包、告诉服务端调用的哪个服务，参数是什么
 6、结果返回给server stub。
 7、sever stub把结果进行打包交给socket
 8、socket通过网络传输消息
 9、client slub 从socket拿到消息。
 10、client stub解包消息将结果返回给client。&lt;/p&gt;

&lt;p&gt;一个RPC框架就是把步骤2到9都封装起来。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;关键点&lt;/strong&gt; &lt;strong&gt;关键点&lt;/strong&gt; &lt;strong&gt;关键点&lt;/strong&gt;！！！！！&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RPC是远端过程调用，其调用协议通常包含传输协议和序列化协议。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;传输协议包含: 如著名的 &lt;a href=&quot;[grpc / grpc.io](https://link.zhihu.com/?target=http%3A//www.grpc.io/)&quot;&gt;gRPC&lt;/a&gt; 使用的 http2 协议（所以RPC框架也不都是使用自定义的传输协议），也有如dubbo一类的自定义报文的tcp协议。&lt;/p&gt;

&lt;p&gt;序列化协议包含: 如基于文本编码的 xml json，也有二进制编码的 protobuf hessian等。&lt;/p&gt;

&lt;h3 id=&quot;为什么需要rpc&quot;&gt;为什么需要RPC&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;首先要明确一点：RPC可以用HTTP协议实现，并且用HTTP是建立在 TCP 之上最广泛使用的 RPC，但是互联网公司往往用自己的私有协议，比如鹅厂的JCE协议，私有协议不具备通用性为什么还要用呢？因为&lt;strong&gt;相比于HTTP协议，RPC采用二进制字节码传输，更加高效也更加安全。&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;现在业界提倡“微服务“的概念，而服务之间通信目前有两种方式，RPC就是其中一种。RPC可以保证不同服务之间的互相调用。即使是跨语言跨平台也不是问题，让构建分布式系统更加容易。&lt;/li&gt;
  &lt;li&gt;RPC框架都会有服务降级、流量控制的功能，保证服务的&lt;strong&gt;高可用&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;序列化和io模型的优化&quot;&gt;序列化和I/O模型的优化&lt;/h3&gt;

&lt;p&gt;通过优化java原生支持的序列化及IO创建的RPC功能，可以通过以下两点进行优化。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;数据序列化：&lt;/strong&gt;
什么是序列化？序列化就是编码的过程，把对象或者数据结构转化成二进制字节码的过程。而反序列化就是把二进制字节码转化成数据结构或者对象。只有经过序列化后的数据才能在网络中传输。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;I/O模型：&lt;/strong&gt;
客户端和服务端的通信依赖Socket I/O。I/O 模型又可以分为：
    &lt;ul&gt;
      &lt;li&gt;传统的阻塞 I/O（Blocking I/O）&lt;/li&gt;
      &lt;li&gt;非阻塞 I/O（Non-blocking I/O）&lt;/li&gt;
      &lt;li&gt;I/O 多路复用（I/O multiplexing）&lt;/li&gt;
      &lt;li&gt;异步 I/O（Asynchronous I/O）&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;用protobuf优化数据序列化&quot;&gt;用Protobuf优化数据序列化&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;https://link.jianshu.com/?t=http%3A%2F%2Fblog.csdn.net%2Fzxhoo%2Farticle%2Fdetails%2F53228303&quot;&gt;图解Protobuf编码&lt;/a&gt;以及&lt;a href=&quot;https://link.jianshu.com/?t=http%3A%2F%2Fblog.csdn.net%2Fcarson_ho%2Farticle%2Fdetails%2F70568606%2F&quot;&gt;Protocol Buffer 序列化原理大揭秘&lt;/a&gt;。&lt;/p&gt;

&lt;h4 id=&quot;用netty优化io模型&quot;&gt;用Netty优化I/O模型&lt;/h4&gt;

&lt;p&gt;Netty 正是采用了第三种 I/O多路复用的方法，I/O多路复用对应Reactor模式。Reactor把耗时的网络操作编码交给专门的线程或者线程池去处理。比如下面这张图是Reactor模式示意图。图中mainReactor线程、subReactor线程、work线程这些不同的线程，分别干不同专业的事情，吞吐量自然上去了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://tva1.sinaimg.cn/large/006y8mN6ly1g69dokdt5rj30j10dlmzf.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里要再多说一句异步I/O。&lt;/p&gt;

&lt;p&gt;前面提到的三种I/O模型都归属于同步I/O，用户发起I/O请求后需要等待或者轮询内核I/O的完成。PHP中的&lt;a href=&quot;https://link.jianshu.com?t=https%3A%2F%2Fwww.swoole.com%2F&quot;&gt;swoole框架&lt;/a&gt;, 一款异步网络通信框架。当时我第一次听到异步I/O的感到很奇怪，因为之前看到有些文章里都有说到异步I/O往往对应的是Proactor模式，而Proactor在Linix中没有很好的实现。那么Swoole是如何实现异步I/O。&lt;/p&gt;

&lt;p&gt;这里就要提&lt;strong&gt;协程&lt;/strong&gt;的概念了。协程可以理解为用户态的线程，他有两个特点：1、占用的资源更少。2、所有切换和调度都发生在用户态。Swoole底层就是借鉴了Go语言的协程，而Go语言之所有能受到关注和部分青睐，也是因为他引入了协程。这里特别要推荐知乎专栏里的&lt;a href=&quot;https://link.jianshu.com?t=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F27590299&quot;&gt;协程，高并发IO的终级杀器&lt;/a&gt;的文章，通过简单的例子帮你理解协程。&lt;/p&gt;

&lt;h3 id=&quot;restful&quot;&gt;RestFul&lt;/h3&gt;

&lt;p&gt;操作对象即为资源，对资源的四种操作（post、get、put、delete），并且参数都放在URL上,但是不严格的说Http+json、Http+xml，常见的http api都可以称为Rest接口。&lt;/p&gt;

&lt;h3 id=&quot;restful与rpc比较&quot;&gt;RestFul与RPC比较&lt;/h3&gt;

&lt;p&gt;服务之间通信目前有两种方式RestFul与RPC&lt;/p&gt;

&lt;p&gt;通常，在对外开放的接口服务中，使用restful方式。因为restFul采用http通信协议，http相对更规范，更标准，更通用，无论哪种语言都支持http协议。如果你是对外开放API，例如开放平台，外部的编程语言多种多样，你无法拒绝对每种语言的支持，相应的，如果采用http，无疑在你实现SDK之前，支持了所有语言，所以，现在开源中间件，基本最先支持的几个协议都包含RESTful。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;分布式系统间通信通&lt;/strong&gt;方式常包含两个部分，序列化和通信协议。常见的序列化协议包括json、xml、hession、protobuf、thrift、text、bytes等；通信协议比较流行的是http、soap、websockect。RPC通常基于TCP实现，常用框架例如netty。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;RESTful通常采用http+JSON实现。&lt;/li&gt;
  &lt;li&gt;RPC是指通信协议采用二进制方式，而不是http；序列化采用JSON，protobuf，hession等形式的。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;自定义tcp协议和http协议的区别选择&quot;&gt;自定义TCP协议和HTTP协议的区别/选择&lt;/h3&gt;

&lt;h4 id=&quot;rpc自定义tcp协议和http协议的区别&quot;&gt;RPC自定义TCP协议和HTTP协议的区别&lt;/h4&gt;

&lt;p&gt;&lt;u&gt;RPC要使用自定义的TCP协议和HTTP协议的区别在于，Http1.1协议的http报文中有太多的废信息，导致传输效率低；使用自定义的则可以缩短报文长度&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;通用定义的http1.1协议的tcp报文包含太多废信息，一个POST协议的格式大致如下&lt;/p&gt;

    &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;HTTP/1.0 200 OK 
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84
  
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;Hello World&lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;即使编码协议也就是body是使用二进制编码协议，报文元数据也就是header头的键值对却用了文本编码，非常占字节数。如上图所使用的报文中有效字节数仅仅占约 30%，也就是70%的时间用于传输元数据废编码。当然实际情况下报文内容可能会比这个长，但是报头所占的比例也是非常可观的。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;那么假如我们使用自定义tcp协议的报文如下&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;https://ws3.sinaimg.cn/large/d8b81fbfly1g1ah4btzblj20k0033aa4.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;报头占用的字节数也就只有16个byte，极大地精简了传输内容。&lt;/p&gt;

    &lt;p&gt;==这也就是为什么后端进程间通常会采用自定义tcp协议的rpc来进行通信的原因。==&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;否认两点：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;http 协议相较于自定义tcp报文协议，增加的开销在于连接的建立与断开。http协议是支持连接池复用的，也就是建立一定数量的连接不断开，并不会频繁的创建和销毁连接。&lt;/li&gt;
  &lt;li&gt;http也可以使用protobuf这种二进制编码协议对内容进行编码，因此二者最大的区别还是在传输协议上。&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;如何选择&quot;&gt;如何选择？&lt;/h4&gt;

&lt;p&gt;什么场景使用文本协议http 什么时候使用自定义二进制协议？&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;http适用短链接，高延迟，大包通讯，频率低，业务层无需维持长连接以及状态的程序 。例如起点阅读网站&lt;/li&gt;
  &lt;li&gt;自定义二进制协议主要用于：长链接，低延迟，小包通讯频率极高，业务层要求长连接以及状态的程序。 例如游戏服务器位置同步 moba类游戏50ms同步一次&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外 协议的选择前后端最好要一致 大部分人使用http协议的原因是因为主流网页游览器支持http协议 而且它有完善的安全机制 例如 token cqs这些 所以本身http这个协议及很完美 后端没得选择 只能用http协议 直连http客户端的后端服务器其实可以使用dubbo grpc调用真正做事的逻辑服务器 追求单机最大负载&lt;/p&gt;

&lt;h3 id=&quot;参考&quot;&gt;参考&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://www.jianshu.com/p/32ca4fd5a7e2&quot;&gt;从一个简单例子聊RPC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://infoq.cn/article/get-to-know-rpc&quot;&gt;体系化认识 RPC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.blogjava.net/DLevin/archive/2015/09/02/427045.html&quot;&gt;Reactor模式详解&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;http://developer.51cto.com/art/201906/597963.htm&lt;/p&gt;
</description>
        <pubDate>Thu, 07 Mar 2019 00:00:00 +0000</pubDate>
        <link>http://offery-gong.github.io/2019/03/07/RPC&Rest/</link>
        <guid isPermaLink="true">http://offery-gong.github.io/2019/03/07/RPC&Rest/</guid>
        
        <category>分布式技术</category>
        
        
      </item>
    
      <item>
        <title>JavaIO-NIO-AIO</title>
        <description>&lt;p&gt;在通信编程中，涉及到三个层次的内容：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;语言层面的IO&lt;/li&gt;
  &lt;li&gt;操作系统层面IO&lt;/li&gt;
  &lt;li&gt;IO操作在计算机网络的实现&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在下文，我们首先介绍关于IO的基础知识（数据单位；Socket与TCP/UDP；Socket与操作系统）；之后我们从宏观角度介绍在操作系统层面的五种IO模型和java语言层面的IO架构（主要是传统的IO包）；然后分别介绍操作系统层面的五种IO模型中（BIO，NIO，IO复用）的具体原理。&lt;/p&gt;

&lt;h2 id=&quot;一基本知识&quot;&gt;一、基本知识&lt;/h2&gt;

&lt;h3 id=&quot;1数据单位&quot;&gt;1.数据单位&lt;/h3&gt;

&lt;p&gt;bit：位（二进制的一个“0”或一个“1”叫一位。）&lt;/p&gt;

&lt;p&gt;byte： 1字节=8bit（它是一个8位的二进制数，是一个很具体的存储空间）&lt;/p&gt;

&lt;p&gt;char： 字符&lt;/p&gt;

&lt;p&gt;ASCII码：ASCII码中，一个英文字母（不分大小写）占一个字节的空间，一个中文汉字占两个字节的空间。GB2312 是对 ASCII 的中文扩展。&lt;/p&gt;

&lt;p&gt;Unicode字符集：常用的是用&lt;strong&gt;两个字节&lt;/strong&gt;（16位）表示一个字符（如果要用到非常偏僻的字符，就需要4个字节）。&lt;/p&gt;

&lt;p&gt;UTF-8编码规则：一种变长的编码方式：它可以使用1~4个字节表示一个符号，根据不同的符号而变化字节长度。当字符在ASCII码的范围时，就用一个字节表示，保留了ASCII字符一个字节的编码做为它的一部分，如此一来UTF-8编码也可以是为视为一种对ASCII码的拓展。值得注意的是unicode编码中一个中文字符占2个字节，而UTF-8一个中文字符占3个字节。从unicode到uft-8并不是直接的对应，而是要过一些算法和规则来转换。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UTF-8&lt;/strong&gt;就是每次8个位传输数据，而&lt;strong&gt;UTF-16&lt;/strong&gt;就是每次16个位。UTF-8就是在互联网上使用最广的一种unicode的实现方式，这是为传输而设计的编码。&lt;/p&gt;

&lt;p&gt;&lt;u&gt;在计算机内存中，统一使用Unicode编码，当需要保存到硬盘或者需要传输的时候，就转换为UTF-8编码。&lt;/u&gt;&lt;/p&gt;

&lt;h3 id=&quot;同步-vs-异步&quot;&gt;同步 vs. 异步&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;同步I/O&lt;/strong&gt;　每个请求必须逐个地被处理，一个请求的处理会导致整个流程的暂时等待，这些事件无法并发地执行。用户线程发起I/O请求后需要等待或者轮询内核I/O操作完成后才能继续执行。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;异步I/O&lt;/strong&gt;　多个请求可以并发地执行，一个请求或者任务的执行不会导致整个流程的暂时等待。用户线程发起I/O请求后仍然继续执行，当内核I/O操作完成后会通知用户线程，或者调用用户线程注册的回调函数。&lt;/p&gt;

&lt;h3 id=&quot;阻塞-vs-非阻塞&quot;&gt;阻塞 vs. 非阻塞&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;阻塞&lt;/strong&gt;　某个请求发出后，由于该请求操作需要的条件不满足，请求操作一直阻塞，不会返回，直到条件满足。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;非阻塞&lt;/strong&gt;　请求发出后，若该请求需要的条件不满足，则立即返回一个标志信息告知条件不满足，而不会一直等待。一般需要通过循环判断请求条件是否满足来获取请求结果。&lt;/p&gt;

&lt;p&gt;需要注意的是，阻塞并不等价于同步，而非阻塞并非等价于异步。事实上这两组概念描述的是I/O模型中的两个不同维度。&lt;/p&gt;

&lt;p&gt;同步和异步着重点在于多个任务执行过程中，后发起的任务是否必须等先发起的任务完成之后再进行。而不管先发起的任务请求是阻塞等待完成，还是立即返回通过循环等待请求成功。&lt;/p&gt;

&lt;p&gt;而阻塞和非阻塞重点在于请求的方法是否立即返回（或者说是否在条件不满足时被阻塞）&lt;/p&gt;

&lt;h3 id=&quot;2socket&quot;&gt;2.Socket&lt;/h3&gt;

&lt;p&gt;创建socket的语句时，操作系统会创建一个由文件系统管理的socket对象（如下图）。这个socket对象包含了发送缓冲区、接收缓冲区、等待队列等成员。等待队列是个非常重要的结构，它指向所有需要等待该socket事件的进程。&lt;/p&gt;

&lt;h3 id=&quot;3socket与tcp通信&quot;&gt;3.Socket与TCP通信&lt;/h3&gt;

&lt;h4 id=&quot;tcp与udp的socket缓冲区&quot;&gt;TCP与UDP的socket缓冲区&lt;/h4&gt;

&lt;p&gt;首先，对于 TCP 通信来说，每个 TCP Socket 在内核中都有一个&lt;strong&gt;发送缓冲区&lt;/strong&gt;和一个&lt;strong&gt;接收缓冲区&lt;/strong&gt;，TCP 的全双工的工作模式及 TCP 的滑动窗口便依赖于这两个独立的 Buffer 及此 Buffer 的填充状态。&lt;/p&gt;

&lt;p&gt;接收缓冲区把数据缓存入内核，若应用进程一直没有调用 Socket 的 read 方法进行读取的话，则此数据会一直被缓存在接收缓冲区内。不管进程是否读取 Socket，对端发来的数据都会经由内核接收并且缓存到 Socket 的内核接收缓冲区中。read 所做的工作，就是把内核接收缓冲区中的数据复制到应用层用户的 Buffer 里面，仅此而已。&lt;/p&gt;

&lt;p&gt;进程调用 Socket 的 send 发送数据的时候，最简单的情况（也是一般情况）是将数据从应用层用户的 Buffer 里复制到 Socket 的内核发送缓冲区中，然后 send 便会在上层返回。换句话说，send 返回时，数据不一定会被发送到对端（和 write写文件有点类似），send 仅仅是把应用层 Buffer 的数据复制到 Socket 的内核发送 Buffer 中。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;而对于 UDP 通信来说，每个 UDP Socket 都有一个接收缓冲区，而没有发送缓冲区&lt;/strong&gt;，从概念上来说就是只要有数据就发，不管对方是否可以正确接收，所以不缓冲，不需要发送缓冲区。&lt;/p&gt;

&lt;h4 id=&quot;流量控制&quot;&gt;流量控制&lt;/h4&gt;

&lt;p&gt;我们来说说 TCP/IP 的滑动窗口和流量控制机制，前面我们提到，Socket 的接收缓冲区被 TCP 和 UDP 用来缓存网络上收到的数据，一直保存到应用进程读走为止。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;对于 TCP 来说，如果应用进程一直没有读取，则 Buffer 满了之后，发生的动作是：通知对端 TCP 协议中的窗口关闭，保证 TCP 套接口接收缓冲区不会溢出，保证了 TCP 是可靠传输的，这个便是滑动窗口的实现。因为对方不允许发出超过通告窗口大小的数据，所以如果对方无视窗口大小而发出了超过窗口大小的数据，则接收方 TCP 将丢弃它，这就是 TCP 的流量控制原理。&lt;/li&gt;
  &lt;li&gt;对于 UDP 来说，当接收方的 Socket 接收缓冲区满时，新来的数据报无法进入接收缓冲区，此数据报就会被丢弃，UDP 是没有流量控制的，快的发送者可以很容易地淹没慢的接收者，导致接收方的 UDP丢弃数据报。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;4socket与linux操作系统&quot;&gt;4.Socket与Linux操作系统&lt;/h3&gt;

&lt;p&gt;在Linux世界，“一切皆文件”，操作系统把网络读写作为IO操作，就像读写文件那样，对外提供出来的编程接口就是Socket。所以，socket（套接字）是通信的基石，是支持TCP/IP协议网络通信的基本操作单元。socket实质上提供了进程通信的端点。进程通信之前，双方首先必须各自创建一个端点，否则是没有办法建立联系并相互通信的。一个完整的socket有一个本地唯一的socket号，这是由操作系统分配的。&lt;/p&gt;

&lt;p&gt;从设计模式的角度看， Socket其实是一个外观模式，它把复杂的TCP/IP协议栈隐藏在Socket接口后面，对用户来说，一组简单的Socket接口就是全部。当应用程序创建一个socket时，操作系统就返回一个整数作为描述符（descriptor）来标识这个套接字。然后，应用程序以该描述符为传递参数，通过调用函数来完成某种操作（例如通过网络传送数据或接收输入的数据）。以TCP 为例，典型的Socket 使用如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww2.sinaimg.cn/large/006tNc79ly1g5omrfmeqwj30da0dnjsf.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在许多操作系统中，Socket描述符和其他I/O描述符是集成在一起的，操作系统把socket描述符实现为一个指针数组，这些指针指向内部数据结构。进一步看，操作系统为每个运行的进程维护一张单独的文件描述符表。当进程打开一个文件时，系统把一个指向此文件内部数据结构的指针写入文件描述符表，并把该表的索引值返回给调用者。&lt;/p&gt;

&lt;p&gt;既然Socket和操作系统的IO操作相关，那么各操作系统IO实现上的差异会导致Socket编程上的些许不同。看看我Mac上的Socket.so 会发现和CentOS上的还是些不同的。&lt;/p&gt;

&lt;p&gt;进程进行Socket操作时，也有着多种处理方式，如阻塞式IO，非阻塞式IO，多路复用(select/poll/epoll)，AIO等等。下一节将介绍操作系统的五种IO模型。&lt;/p&gt;

&lt;p&gt;**&lt;a href=&quot;https://blog.csdn.net/linxdcn/article/details/72903422&quot;&gt;ps.系统传统的IO调用过程&lt;/a&gt; **&lt;/p&gt;

&lt;p&gt;以下是操作系统一般情况下的IO调用。在传统的文件IO操作中，我们都是调用操作系统提供的底层标准IO系统调用函数 read()、write() ，此时调用此函数的进程（在JAVA中即java进程）由当前的&lt;strong&gt;用户态切换到内核态&lt;/strong&gt;，然后OS的内核代码负责将相应的文件数据读取到内核的IO缓冲区，然后再把数据从内核IO缓冲区拷贝到进程的私有地址空间中去，这样便完成了一次IO操作。如下图所示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://img-blog.csdn.net/20170607224313512?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGlueGRjbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;二操作系统的五种io模型&quot;&gt;二、操作系统的五种IO模型&lt;/h2&gt;

&lt;h3 id=&quot;javaio与操作系统io的联系&quot;&gt;JavaIO与操作系统IO的联系&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;在Java中，主要有三种IO模型，分别是&lt;strong&gt;阻塞IO（BIO）、非阻塞IO（NIO）和 异步IO（AIO）&lt;/strong&gt;。&lt;/li&gt;
  &lt;li&gt;在Linux(UNIX)操作系统中，共有五种IO模型，分别是：&lt;strong&gt;阻塞IO模型&lt;/strong&gt;、&lt;strong&gt;非阻塞IO模型&lt;/strong&gt;、&lt;strong&gt;IO复用模型&lt;/strong&gt;、&lt;strong&gt;信号驱动IO模型&lt;/strong&gt;以及&lt;strong&gt;异步IO模型&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Java中提供的IO有关的API，在文件处理的时候，其实依赖操作系统层面的IO操作实现的。&lt;/strong&gt;比如在Linux 2.6以后，Java中NIO和AIO都是通过epoll来实现的，而在Windows上，AIO是通过IOCP来实现的。&lt;/p&gt;

&lt;p&gt;可以把Java中的BIO、NIO和AIO理解为是Java语言对操作系统的各种IO模型的封装。程序员在使用这些API的时候，不需要关心操作系统层面的知识，也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。&lt;/p&gt;

&lt;p&gt;一次IO过程：文件从硬盘中拷贝到用户空间中，中间过渡的空间映射成内核空间。&lt;/p&gt;

&lt;h3 id=&quot;阻塞io模型&quot;&gt;&lt;strong&gt;阻塞IO模型&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;阻塞 I/O 是最简单的 I/O 模型，一般表现为进程或线程等待某个条件，如果条件不满足，则一直等下去。条件满足，则进行下一步操作。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww2.sinaimg.cn/large/006tNc79ly1g5omsl5dsrj30h909e0tb.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;应用进程通过系统调用 &lt;code class=&quot;highlighter-rouge&quot;&gt;recvfrom&lt;/code&gt; 接收数据，但由于内核还未准备好数据报，应用进程就会阻塞住，直到内核准备好数据报，&lt;code class=&quot;highlighter-rouge&quot;&gt;recvfrom&lt;/code&gt; 完成数据报复制工作，应用进程才能结束阻塞状态。&lt;/p&gt;

&lt;p&gt;这种钓鱼方式相对来说比较简单，对于钓鱼的人来说，不需要什么特制的鱼竿，拿一根够长的木棍就可以悠闲的开始钓鱼了（实现简单）。缺点就是比较耗费时间，比较适合那种对鱼的需求量小的情况（并发低，时效性要求低）。&lt;/p&gt;

&lt;h3 id=&quot;非阻塞io模型&quot;&gt;&lt;strong&gt;非阻塞IO模型&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;我们钓鱼的时候，在等待鱼儿咬钩的过程中，我们可以做点别的事情，比如玩一把王者荣耀、看一集《延禧攻略》等等。但是，我们要时不时的去看一下鱼竿，一旦发现有鱼儿上钩了，就把鱼钓上来。&lt;/p&gt;

&lt;p&gt;映射到Linux操作系统中，这就是非阻塞的IO模型。应用进程与内核交互，目的未达到之前，不再一味的等着，而是直接返回。然后通过轮询的方式，不停的去问内核数据准备有没有准备好。如果某一次轮询发现数据已经准备好了，那就把数据拷贝到用户空间中。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww3.sinaimg.cn/large/006tNc79ly1g5omt3r1ygj30ji0aeab4.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;应用进程通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;recvfrom&lt;/code&gt; 调用不停的去和内核交互，直到内核准备好数据。如果没有准备好，内核会返回&lt;code class=&quot;highlighter-rouge&quot;&gt;error&lt;/code&gt;，应用进程在得到&lt;code class=&quot;highlighter-rouge&quot;&gt;error&lt;/code&gt;后，过一段时间再发送&lt;code class=&quot;highlighter-rouge&quot;&gt;recvfrom&lt;/code&gt;请求。在两次发送请求的时间段，进程可以先做别的事情。&lt;/p&gt;

&lt;h3 id=&quot;信号驱动io模型&quot;&gt;&lt;strong&gt;信号驱动IO模型&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;我们钓鱼的时候，为了避免自己一遍一遍的去查看鱼竿，我们可以给鱼竿安装一个报警器。当有鱼儿咬钩的时候立刻报警。然后我们再收到报警后，去把鱼钓起来。&lt;/p&gt;

&lt;p&gt;映射到Linux操作系统中，这就是信号驱动IO。应用进程在读取文件时通知内核，如果某个 socket 的某个事件发生时，请向我发一个信号。在收到信号后，信号对应的处理函数会进行后续处理。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww3.sinaimg.cn/large/006tNc79ly1g5omtj989uj30jq0avt9l.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;应用进程预先向内核注册一个信号处理函数，然后用户进程返回，并且不阻塞，当内核数据准备就绪时会发送一个信号给进程，用户进程便在信号处理函数中开始把数据拷贝的用户空间中。&lt;/p&gt;

&lt;h3 id=&quot;io复用模型&quot;&gt;&lt;strong&gt;IO复用模型&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;我们钓鱼的时候，为了保证可以最短的时间钓到最多的鱼，我们同一时间摆放多个鱼竿，同时钓鱼。然后哪个鱼竿有鱼儿咬钩了，我们就把哪个鱼竿上面的鱼钓起来。&lt;/p&gt;

&lt;p&gt;映射到Linux操作系统中，这就是IO复用模型。多个进程的IO可以注册到同一个管道上，这个管道会统一和内核进行交互。&lt;strong&gt;当管道中的某一个请求需要的数据准备好之后，进程再把对应的数据拷贝到用户空间中。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww1.sinaimg.cn/large/006tNc79ly1g5omultsn1j30jq0adjsc.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;IO多路转接是多了一个&lt;code class=&quot;highlighter-rouge&quot;&gt;select&lt;/code&gt;函数，多个进程的IO可以注册到同一个&lt;code class=&quot;highlighter-rouge&quot;&gt;select&lt;/code&gt;上，当用户进程调用该&lt;code class=&quot;highlighter-rouge&quot;&gt;select&lt;/code&gt;，&lt;code class=&quot;highlighter-rouge&quot;&gt;select&lt;/code&gt;会监听所有注册好的IO，如果所有被监听的IO需要的数据都没有准备好时，&lt;code class=&quot;highlighter-rouge&quot;&gt;select&lt;/code&gt;调用进程会阻塞。当任意一个IO所需的数据准备好之后，&lt;code class=&quot;highlighter-rouge&quot;&gt;select&lt;/code&gt;调用就会返回，然后进程在通过&lt;code class=&quot;highlighter-rouge&quot;&gt;recvfrom&lt;/code&gt;来进行数据拷贝。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;这里的IO复用模型，并没有向内核注册信号处理函数，所以，他并不是非阻塞的。&lt;/strong&gt;进程在发出&lt;code class=&quot;highlighter-rouge&quot;&gt;select&lt;/code&gt;后，要等到&lt;code class=&quot;highlighter-rouge&quot;&gt;select&lt;/code&gt;监听的所有IO操作中至少有一个需要的数据准备好，才会有返回，并且也需要再次发送请求去进行文件的拷贝。&lt;/p&gt;

&lt;h3 id=&quot;同步io模型&quot;&gt;同步IO模型&lt;/h3&gt;

&lt;p&gt;我们说阻塞IO模型、非阻塞IO模型、IO复用模型和信号驱动IO模型都是同步的IO模型。原因是因为，无论以上那种模型，真正的数据拷贝过程，都是同步进行的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;信号驱动难道不是异步的么？&lt;/strong&gt; 信号驱动，内核是在数据准备好之后通知进程，然后进程再通过&lt;code class=&quot;highlighter-rouge&quot;&gt;recvfrom&lt;/code&gt;操作进行数据拷贝。我们可以认为&lt;strong&gt;数据准备阶段是异步的&lt;/strong&gt;，但是，&lt;strong&gt;数据拷贝操作是同步的&lt;/strong&gt;。所以，整个IO过程也不能认为是异步的。&lt;/p&gt;

&lt;h3 id=&quot;异步io模型&quot;&gt;&lt;strong&gt;异步IO模型&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;我们钓鱼的时候，采用一种高科技钓鱼竿，即全自动钓鱼竿。可以自动感应鱼上钩，自动收竿，更厉害的可以自动把鱼放进鱼篓里。然后，通知我们鱼已经钓到了，他就继续去钓下一条鱼去了。&lt;/p&gt;

&lt;p&gt;映射到Linux操作系统中，这就是异步IO模型。应用进程把IO请求传给内核后，完全由内核去操作文件拷贝。内核完成相关操作后，会发信号告诉应用进程本次IO已经完成。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww3.sinaimg.cn/large/006tNc79ly1g5omuzoz45j30jd0bogma.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;用户进程发起&lt;code class=&quot;highlighter-rouge&quot;&gt;aio_read&lt;/code&gt;操作之后，给内核传递描述符、缓冲区指针、缓冲区大小等，告诉内核当整个操作完成时，如何通知进程，然后就立刻去做其他事情了。当内核收到&lt;code class=&quot;highlighter-rouge&quot;&gt;aio_read&lt;/code&gt;后，会立刻返回，然后内核开始等待数据准备，数据准备好以后，直接把数据拷贝到用户控件，然后再通知进程本次IO已经完成。&lt;/p&gt;

&lt;p&gt;==最新的AIO(Async I/O)里面会更进一步：不但等待就绪是非阻塞的，就连数据从网卡到内存的过程也是异步的。==&lt;/p&gt;

&lt;h3 id=&quot;5种io模型对比&quot;&gt;&lt;strong&gt;5种IO模型对比&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/77752ed5.jpg&quot; alt=&quot;emma_1&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;三java-的-io-类库的基本架构&quot;&gt;三、Java 的 I/O 类库的基本架构&lt;/h2&gt;

&lt;p&gt;Java 的 I/O 操作类在包 java.io 下，大概有将近 80 个类，但是这些类大概可以分成四组，分别是：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;基于字节操作的 I/O 接口：InputStream 和 OutputStream&lt;/li&gt;
  &lt;li&gt;基于字符操作的 I/O 接口：Writer 和 Reader&lt;/li&gt;
  &lt;li&gt;基于磁盘操作的 I/O 接口：File&lt;/li&gt;
  &lt;li&gt;基于网络操作的 I/O 接口：Socket&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;前两组主要是根据传输数据的数据格式，后两组主要是根据传输数据的方式&lt;/strong&gt;。虽然 Socket 类并不在 java.io 包下，但是我仍然把它们划分在一起，因为我个人认为 I/O 的核心问题要么是数据格式影响 I/O 操作，要么是传输方式影响 I/O 操作，也就是将什么样的数据写到什么地方的问题，I/O 只是人与机器或者机器与机器交互的手段，除了在它们能够完成这个交互功能外，我们关注的就是如何提高它的运行效率了，而数据格式和传输方式是影响效率最关键的因素了。我们后面的分析也是基于这两个因素来展开的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.ibm.com/developerworks/cn/java/j-lo-javaio/image002.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.ibm.com/developerworks/cn/java/j-lo-javaio/image004.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;注意两点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;一个是操作数据的方式是可以组合使用的（就是指节点流和处理流的问题），如这样组合使用:&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;OutputStream out = new BufferedOutputStream(new ObjectOutputStream(new FileOutputStream(&quot;fileName&quot;))&lt;/code&gt;；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;流最终写到什么地方必须要指定，要么是写到磁盘要么是写到网络中。其实从上面的类图中我们发现，&lt;strong&gt;写网络实际上也是写文件&lt;/strong&gt;，只不过写网络还有一步需要处理：底层操作系统再将数据传送到其它地方而不是本地磁盘。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;四bio介绍&quot;&gt;四、BIO介绍&lt;/h2&gt;

&lt;h3 id=&quot;1分类&quot;&gt;1.分类&lt;/h3&gt;

&lt;h4 id=&quot;按照流的流向分可以分为输入流和输出流&quot;&gt;按照流的流向分，可以分为输入流和输出流。&lt;/h4&gt;

&lt;p&gt;IO的输入输出是根据数据进/出&lt;strong&gt;内存&lt;/strong&gt;来判断的：进入内存为输入，出内存为输出。对于如图15.1所示的数据流向，数据从内存到硬盘，通常称为输出流；&lt;/p&gt;

&lt;p&gt;对于如图15.2所示的数据流向，数据从&lt;a href=&quot;https://www.baidu.com/s?wd=%E6%9C%8D%E5%8A%A1%E5%99%A8&amp;amp;tn=24004469_oem_dg&amp;amp;rsv_dl=gh_pl_sl_csd&quot;&gt;服务器&lt;/a&gt;通过网络流向客户端，在这种情况下,Server端的内存负责将数据输出到网络里，因此Server端的程序使用输出流；Client端的内存负责从网络中读取数据，因此Client端的程序应该使用输入流。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww2.sinaimg.cn/large/006tNc79ly1g5omvwxmd6j30nr041jsy.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;注：java的输入流主要是InputStream和Reader作为基类，而输出流则是主要由outputStream和&lt;a href=&quot;https://www.baidu.com/s?wd=Writer&amp;amp;tn=24004469_oem_dg&amp;amp;rsv_dl=gh_pl_sl_csd&quot;&gt;Writer&lt;/a&gt;作为基类。它们都是一些抽象基类，无法直接创建实例。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;按照操作单元划分可以划分为字节流和字符流&quot;&gt;按照操作单元划分，可以划分为字节流和字符流。&lt;/h4&gt;

&lt;p&gt;字节流操作的单元是数据单元是8位的字节，字符流操作的是数据单元为16位的字符。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;字节流主要是由InputStream和outPutStream作为基类，而字符流则主要有Reader和Writer作为基类。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;按照流的角色划分为节点流和处理流&quot;&gt;按照流的角色划分为节点流和处理流。&lt;/h4&gt;

&lt;p&gt;可以从/向一个特定的IO设备（如磁盘，网络）读/写数据的流，称为节点流。节点流也被称为低级流。图15.3显示了节点流的示意图。 
    从图15.3中可以看出，当使用节点流进行输入和输出时，程序直接连接到实际的数据源，和实际的输入/输出节点连接。 
处理流则用于对一个已存在的流进行连接和封装，通过封装后的流来实现数据的读/写功能。处理流也被称为高级流。图15.4显示了处理流的示意图。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww2.sinaimg.cn/large/006tNc79ly1g5omvpzw8dj30po04h0ul.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;从图15.4可以看出，当使用处理流进行输入/输出时，程序并不会直接连接到实际的数据源，没有和实际的输入和输出节点连接。使用处理流的一个明显的好处是，只要使用相同的处理流，程序就可以采用完全相同的输入/输出代码来访问不同的数据源，随着处理流所包装的节点流的变化，程序实际所访问的数据源也相应的发生变化。&lt;/p&gt;

&lt;p&gt;处理流可以“嫁接”在任何已存在的流的基础之上，这就允许Java应用程序采用相同的代码，透明的方式来访问不同的输入和输出设备的数据流。图15.7显示了处理流的模型。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww1.sinaimg.cn/large/006tNc79ly1g5omw4ww1aj30r109zq5q.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;常用的流的分类表&quot;&gt;常用的流的分类表&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;分类&lt;/th&gt;
      &lt;th&gt;字节输入流&lt;/th&gt;
      &lt;th&gt;字节输出流&lt;/th&gt;
      &lt;th&gt;字符输入流&lt;/th&gt;
      &lt;th&gt;字符输出流&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;抽象基类&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;InputStream&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;OutputStream&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Reader&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Writer&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;访问文件&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;FileInputStream&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;FileOutputStream&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;FileReader&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;FileWriter&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;访问数组&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;ByteArrayInputStream&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;ByteArrayOutputStream&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;CharArrayReader&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;CharArrayWriter&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;访问管道&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;PipedInputStream&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;PipedOutputStream&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;PipedReader&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;PipedWriter&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;访问字符串&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;StringReader&lt;/td&gt;
      &lt;td&gt;StringWriter&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;缓冲流&lt;/td&gt;
      &lt;td&gt;BufferedInputStream&lt;/td&gt;
      &lt;td&gt;BufferedOutputStream&lt;/td&gt;
      &lt;td&gt;BufferedReader&lt;/td&gt;
      &lt;td&gt;BufferedWriter&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;转换流&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;InputStreamReader&lt;/td&gt;
      &lt;td&gt;OutputStreamWriter&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;对象流&lt;/td&gt;
      &lt;td&gt;ObjectInputStream&lt;/td&gt;
      &lt;td&gt;ObjectOutputStream&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;/td&gt;
      &lt;td&gt;&lt;em&gt;FilterInputStream&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;FilterOutputStream&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;FilterReader&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;FilterWriter&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;打印流&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;PrintStream&lt;/td&gt;
      &lt;td&gt;PrintWriter&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;推回输入流&lt;/td&gt;
      &lt;td&gt;PushbackInputStream&lt;/td&gt;
      &lt;td&gt;PushbackReader&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;/td&gt;
      &lt;td&gt;DataInputStream&lt;/td&gt;
      &lt;td&gt;DataOutputStream&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;注：表中粗体字所标出的类代表节点流，必须直接与指定的物理节点关联：斜体字标出的类代表抽象基类，无法直接创建实例，其余正常的表示处理流。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;2常用的bio流的用法&quot;&gt;2.常用的BIO流的用法&lt;/h3&gt;

&lt;p&gt;见&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Java%20IO?id=%E4%B8%80%E3%80%81%E6%A6%82%E8%A7%88&quot;&gt;javaIO&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;3传统bio通信模式&quot;&gt;3.传统BIO通信模式&lt;/h3&gt;

&lt;p&gt;BIO通信（一请求一应答）模型图如下&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww4.sinaimg.cn/large/006tNc79ly1g5omweglp6j30ig0chmxl.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;采用 &lt;strong&gt;BIO 通信模型&lt;/strong&gt; 的服务端，通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在&lt;code class=&quot;highlighter-rouge&quot;&gt;while(true)&lt;/code&gt; 循环中服务端会调用 &lt;code class=&quot;highlighter-rouge&quot;&gt;accept()&lt;/code&gt; 方法等待接收客户端的连接的方式监听请求，请求一旦接收到一个连接请求，就可以建立通信套接字在这个通信套接字上进行读写操作，此时不能再接收其他客户端连接请求，只能等待同当前连接的客户端的操作执行完成， 不过可以通过多线程来支持多个客户端的连接，如上图所示。&lt;/p&gt;

&lt;p&gt;如果要让 &lt;strong&gt;BIO 通信模型&lt;/strong&gt; 能够同时处理多个客户端请求，就必须使用多线程（主要原因是&lt;code class=&quot;highlighter-rouge&quot;&gt;socket.accept()&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;socket.read()&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;socket.write()&lt;/code&gt; 涉及的三个主要函数都是同步阻塞的），也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理，处理完成之后，通过输出流返回应答给客户端，线程销毁。这就是典型的 &lt;strong&gt;一请求一应答通信模型&lt;/strong&gt; 。我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销，不过可以通过 &lt;strong&gt;线程池机制&lt;/strong&gt; 改善，线程池还可以让线程的创建和回收成本相对较低。使用&lt;code class=&quot;highlighter-rouge&quot;&gt;FixedThreadPool&lt;/code&gt; 可以有效的控制了线程的最大数量，保证了系统有限的资源的控制，实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型（N 可以远远大于 M），下面一节”伪异步 BIO”中会详细介绍到。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;==当客户端并发访问量增加后这种模型会出现什么问题==&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这个模型最本质的问题在于，严重依赖于线程。但线程是很”贵”的资源，主要表现在：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;线程的创建和销毁成本很高，在Linux这样的操作系统中，线程本质上就是一个进程。创建和销毁都是重量级的系统函数。&lt;/li&gt;
  &lt;li&gt;线程本身占用较大内存，像Java的线程栈，一般至少分配512K～1M的空间，如果系统中的线程数过千，恐怕整个JVM的内存都会被吃掉一半。&lt;/li&gt;
  &lt;li&gt;线程的切换成本是很高的。操作系统发生线程切换的时候，需要保留线程的上下文，然后执行系统调用。如果线程数过高，可能执行线程切换的时间甚至会大于线程执行的时间，这时候带来的表现往往是系统load偏高、CPU sy使用率特别高（超过20%以上)，导致系统几乎陷入不可用的状态。&lt;/li&gt;
  &lt;li&gt;容易造成锯齿状的系统负载。因为系统负载是用活动线程数或CPU核心数，一旦线程数量高但外部网络环境不是很稳定，就很容易造成大量请求的结果同时返回，激活大量阻塞线程从而使系统负载压力过大。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;五伪异步-io&quot;&gt;五、伪异步 IO&lt;/h2&gt;

&lt;p&gt;为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题，后来有人对它的线程模型进行了优化一一后端通过一个线程池来处理多个客户端的请求接入，形成客户端个数M：线程池最大线程数N的比例关系，其中M可以远远大于N.通过线程池可以灵活地调配线程资源，设置线程的最大值，防止由于海量并发接入导致线程耗尽。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://camo.githubusercontent.com/04b258a50ca7f9762f43d64e70f4489440bae4eb/68747470733a2f2f6d792d626c6f672d746f2d7573652e6f73732d636e2d6265696a696e672e616c6979756e63732e636f6d2f332e706e67&quot; alt=&quot;ä¼ªå¼æ­¥IOæ¨¡åå¾&quot; /&gt;&lt;/p&gt;

&lt;p&gt;代码示例：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * @Auther: Goffery Gong
 * @Date: 2019/3/17 17:17
 * @Description: ，连接上服务端8000端口之后，每隔2秒，我们向服务端写一个带有时间戳的 &quot;hello world&quot;
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;Socket&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Socket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getOutputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;: hello world&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getOutputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOServer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;ServerSocket&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serverSocket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ServerSocket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ExecutorService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Executors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;newFixedThreadPool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// (1) 阻塞方法获取新的连接&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Socket&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serverSocket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// (2) 每一个新的连接都创建一个线程，负责读取数据&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;submit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SocketHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SocketHandler&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Runnable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Socket&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;SocketHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Socket&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;InputStream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inputStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// (3) 按字节流方式读取数据&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inputStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;六nio介绍&quot;&gt;六、NIO介绍&lt;/h2&gt;

&lt;p&gt;我们使用InputStream从输入流中读取数据时，如果没有读取到有效的数据，程序将在此处阻塞该线程的执行。其实传统的输入里和输出流都是阻塞式的进行输入和输出。 不仅如此，传统的输入流、输出流都是通过字节的移动来处理的（即使我们不直接处理字节流，但底层实现还是依赖于字节处理），也就是说，面向流的输入和输出一次只能处理一个字节，因此面向流的输入和输出系统效率通常不高。 
    从JDk1.4开始，java提供了一系列改进的输入和输出处理的新功能，这些功能被统称为新IO(NIO)。新增了许多用于处理输入和输出的类，这些类都被放在java.nio包及其子包下，并且对原io的很多类都以NIO为基础进行了改写。新增了满足NIO的功能。 
    &lt;strong&gt;NIO采用了内存映射对象的方式来处理输入和输出，NIO将文件或者文件的一块区域映射到内存中，这样就可以像访问内存一样来访问文件了&lt;/strong&gt;。通过这种方式来进行输入/输出比传统的输入和输出要快的多。&lt;/p&gt;

&lt;h3 id=&quot;nio工作机制&quot;&gt;NIO工作机制&lt;/h3&gt;

&lt;p&gt;原理：NIO由原来的阻塞读写（占用线程）变成了&lt;strong&gt;单线程轮询事件&lt;/strong&gt;，找到可以进行读写的网络描述符进行读写。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww3.sinaimg.cn/large/006tNc79ly1g5omx2sj69j30dy045gll.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Acceptor 负责接收客户端 Socket 发起的新建连接请求，并把该 Socket 绑定到一个 Reactor 线程上，于是这个Socket 随后的读写事件都交给此 Reactor 线程来处理。&lt;/p&gt;

&lt;p&gt;Reactor 线程读取数据后，交给用户程序中的具体 Handler 实现类来完成特定的业务逻辑处理。为了不影响 Reactor 线程，我们通常使用一个单独的线程池来异步执行 Handler 的接口方法。&lt;/p&gt;

&lt;p&gt;但实际上，我们的服务器是多核心的，而且需要高速并发处理大量的客户端连接，&lt;strong&gt;单线程的 Reactor 模型就满足不了需求了，因此我们需要多线程的 Reactor&lt;/strong&gt;。一般原则是 Reactor（线程）的数量与 CPU 核心数（逻辑CPU）保持一致，即每个 CPU 执行一个 Reactor 线程，而客户端的 Socket 连接则随机均分到这些 Reactor 线程上去处理，如果有 8000 个连接，而 CPU 核心数为 8，则平均每个 CPU 核心承担 1000 个连接。&lt;/p&gt;

&lt;p&gt;Channel&lt;/p&gt;

&lt;p&gt;Selectors&lt;/p&gt;

&lt;p&gt;Buffer&lt;/p&gt;

&lt;h3 id=&quot;java-nio&quot;&gt;Java NIO&lt;/h3&gt;

&lt;p&gt;见&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Java%20IO?id=%E4%B8%80%E3%80%81%E6%A6%82%E8%A7%88&quot;&gt;javaNIO&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;七多路复用io&quot;&gt;七、多路复用IO&lt;/h2&gt;

&lt;p&gt;select/poll/epoll 都是 I/O 多路复用的具体实现，select 出现的最早，之后是 poll，再是 epoll。&lt;/p&gt;

&lt;p&gt;好文强烈推荐：&lt;a href=&quot;https://zhuanlan.zhihu.com/p/64138532&quot;&gt;如果这篇文章说不清epoll的本质，那就过来掐死我吧&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;select&quot;&gt;&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Socket?id=select&quot;&gt;select&lt;/a&gt;&lt;/h3&gt;

&lt;h4 id=&quot;select的原理流程&quot;&gt;select的原理流程&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;将需要监视的socket保存到一个数组fds中，之后调用select，如果数组fds中的所有sokect都没有数据，则进程阻塞（将进程添加到所有socket下的等待队列中）&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;http://ww2.sinaimg.cn/large/006tNc79ly1g5ouzt6g8vj30dg09s3yy.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;当socket接收到数据时，从select返回，唤醒进程（所谓唤起进程，就是将进程从所有的等待队列中移除，加入到工作队列里面）；&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;http://ww3.sinaimg.cn/large/006tNc79ly1g5ov0ab3kmj30e70bdaao.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;遍历数组，使用FD_ISSET判断具体哪个socket收到了数据，之后处理；&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;http://ww1.sinaimg.cn/large/006tNc79ly1g5ov1fzjgdj30df09rgm9.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;补充说明： 本节只解释了select的一种情形。当程序调用select时，内核会先遍历一遍socket，如果有一个以上的socket接收缓冲区有数据，那么select直接返回，不会阻塞。这也是为什么select的返回值有可能大于1的原因之一。如果没有socket有数据，进程才会阻塞。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;select调用伪代码&quot;&gt;select调用伪代码&lt;/h4&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd_set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;readfds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd_set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;writefds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd_set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exceptfds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeval&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Copy&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clipboardErrorCopied&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;有三种类型的描述符类型：readset、writeset、exceptset，分别对应读、写、异常条件的描述符集合。fd_set 使用数组实现，数组大小使用 FD_SETSIZE 定义。&lt;/p&gt;

&lt;p&gt;timeout 为超时参数，调用 select 会一直阻塞直到有描述符的事件到达或者等待的时间超过 timeout。&lt;/p&gt;

&lt;p&gt;成功调用返回结果大于 0，出错返回结果为 -1，超时返回结果为 0。&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;fd_set&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd_out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeval&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Reset the sets
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FD_ZERO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd_in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;FD_ZERO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd_out&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Monitor sock1 for input events
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FD_SET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sock1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd_in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Monitor sock2 for output events
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FD_SET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sock2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd_out&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Find out which socket has the largest numeric value as select requires it
&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;largest_sock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sock1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sock2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sock1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sock2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Wait up to 10 seconds
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tv_sec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tv_usec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Call the select，如果没有数据则阻塞
&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;largest_sock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd_out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tv&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Check if select actually succeed，调用成功则遍历数组，找出
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// report error and abort
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// timeout; no event detected
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FD_ISSET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sock1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd_in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// input event on sock1
&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FD_ISSET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sock2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd_out&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;高性能网络编程&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;二&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;：上一个&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;年，著名的&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C10K&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;并发连接问题&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// output event on sock2
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Copy&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clipboardErrorCopied&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;select缺点&quot;&gt;select缺点&lt;/h4&gt;

&lt;p&gt;其一，每次调用select都需要将进程加入到所有监视socket的等待队列，每次唤醒都需要从每个队列中移除。这里涉及了两次遍历，而且每次都要将整个fds列表传递给内核，有一定的开销。正是因为遍历操作开销大，出于效率的考量，才会规定select的最大监视数量，默认只能监视1024个socket。&lt;/p&gt;

&lt;p&gt;其二，进程被唤醒后，程序并不知道哪些socket收到数据，还需要遍历一次。&lt;/p&gt;

&lt;h3 id=&quot;poll&quot;&gt;&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Socket?id=poll&quot;&gt;poll&lt;/a&gt;&lt;/h3&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;poll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pollfd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nfds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Copy&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clipboardErrorCopied&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;pollfd 使用链表实现。&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// The structure for two events
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pollfd&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Monitor sock1 for input
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sock1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;POLLIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Monitor sock2 for output
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sock2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;POLLOUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Wait 10 seconds
&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;poll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10000&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Check if poll actually succeed
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// report error and abort
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// timeout; no event detected
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// If we detect the event, zero it out so we can reuse the structure
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;revents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;POLLIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;revents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// input event on sock1
&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;revents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;POLLOUT&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;revents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// output event on sock2
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Copy&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clipboardErrorCopied&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;select与poll比较&quot;&gt;&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Socket?id=%e6%af%94%e8%be%83&quot;&gt;select与poll比较&lt;/a&gt;&lt;/h3&gt;

&lt;h4 id=&quot;1-功能&quot;&gt;&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Socket?id=_1-%e5%8a%9f%e8%83%bd&quot;&gt;1. 功能&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;select 和 poll 的功能基本相同，不过在一些实现细节上有所不同。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;select 会修改描述符，而 poll 不会；&lt;/li&gt;
  &lt;li&gt;==select 的描述符类型使用数组实现，FD_SETSIZE 大小默认为 1024，因此默认只能监听 1024 个描述符。==如果要监听更多描述符的话，需要修改 FD_SETSIZE 之后重新编译；==而 poll 的描述符类型使用链表实现，没有描述符数量的限制；==&lt;/li&gt;
  &lt;li&gt;poll 提供了更多的事件类型，并且对描述符的重复利用上比 select 高。&lt;/li&gt;
  &lt;li&gt;如果一个线程对某个描述符调用了 select 或者 poll，另一个线程关闭了该描述符，会导致调用结果不确定。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;2-速度&quot;&gt;&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Socket?id=_2-%e9%80%9f%e5%ba%a6&quot;&gt;2. 速度&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;select 和 poll 速度都比较慢&lt;/strong&gt;。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;select 和 poll 每次调用都需要将全部描述符从应用进程缓冲区复制到内核缓冲区&lt;/strong&gt;。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;select 和 poll 的返回结果中没有声明哪些描述符已经准备好，所以如果返回值大于 0 时，应用进程都需要使用轮询的方式来找到 I/O 完成的描述符&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;3-可移植性&quot;&gt;&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Socket?id=_3-%e5%8f%af%e7%a7%bb%e6%a4%8d%e6%80%a7&quot;&gt;3. 可移植性&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;几乎所有的系统都支持 select，但是只有比较新的系统支持 poll。&lt;/p&gt;

&lt;h3 id=&quot;epoll&quot;&gt;&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Socket?id=epoll&quot;&gt;epoll&lt;/a&gt;&lt;/h3&gt;

&lt;h4 id=&quot;1epoll原理和流程&quot;&gt;1.epoll原理和流程&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;进程调用&lt;code class=&quot;highlighter-rouge&quot;&gt;epoll_create&lt;/code&gt;创建eventpoll对象（也就是程序中epfd所代表的对象）&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;维护监视列表：创建epoll对象后，可以用&lt;code class=&quot;highlighter-rouge&quot;&gt;epoll_ctl&lt;/code&gt;添加或删除所要监听的socket，内核会将eventpoll添加到&lt;strong&gt;socket的等待队列中&lt;/strong&gt;；&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;监视列表的数据结构：红黑树&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;当socket收到数据后，中断程序会给eventpoll的“就绪列表”添加socket引用；&lt;strong&gt;中断程序会操作eventpoll对象，而不是直接操作进程&lt;/strong&gt;（对比上面select图，加入到socket等待队列中的是进程，而epoll则是eventpoll对象）。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;当程序执行到&lt;code class=&quot;highlighter-rouge&quot;&gt;epoll_wait&lt;/code&gt;时，如果rdlist已经引用了socket，那么epoll_wait直接返回，如果rdlist为空，阻塞进程&lt;/p&gt;

    &lt;ol&gt;
      &lt;li&gt;
        &lt;p&gt;进程阻塞和唤醒进程：假设计算机中正在运行进程A和进程B，在某时刻进程A运行到了epoll_wait语句。如下图所示，内核会将进程A放入eventpoll的等待队列中，阻塞进程&lt;/p&gt;

        &lt;p&gt;&lt;img src=&quot;http://ww1.sinaimg.cn/large/006tNc79ly1g5ovs24n3fj30er0bxq3w.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;当socket接收到数据，中断程序一方面修改rdlist，另一方面唤醒eventpoll等待队列中的进程，进程A再次进入运行状态（如下图）。也因为rdlist的存在，进程A可以知道哪些socket发生了变化&lt;/p&gt;

        &lt;p&gt;&lt;img src=&quot;http://ww2.sinaimg.cn/large/006tNc79ly1g5ovsiashrj30ev0by3zl.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;2-实现细节&quot;&gt;2. 实现细节&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;eventpoll对象中就序列表rdlist的数据结构：双向队列&lt;/li&gt;
  &lt;li&gt;eventpoll对象中，维护监视队列的数据结构（rbr）：红黑树。需要满足快速插入，删除，检索（避免重复）&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;ps：因为操作系统要兼顾多种功能，以及由更多需要保存的数据，rdlist并非直接引用socket，而是通过epitem间接引用，红黑树的节点也是epitem对象。同样，文件系统也并非直接引用着socket。为方便理解，本文中省略了一些间接结构。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;http://ww3.sinaimg.cn/large/006tNc79ly1g5ow80h4foj30ep0cv75b.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;3epoll调用伪代码&quot;&gt;3.epoll调用伪代码&lt;/h4&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoll_create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoll_ctl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epfd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoll_event&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;；&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoll_wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epfd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoll_event&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxevents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Copy&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clipboardErrorCopied&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;epoll_ctl() 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上，通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理，进程调用 epoll_wait() 便可以得到事件完成的描述符。&lt;/p&gt;

&lt;p&gt;从上面的描述可以看出，epoll 只需要将描述符从进程缓冲区向内核缓冲区拷贝一次，并且进程不需要通过轮询来获得事件完成的描述符。&lt;/p&gt;

&lt;p&gt;epoll 仅适用于 Linux OS。&lt;/p&gt;

&lt;p&gt;epoll 比 select 和 poll 更加灵活而且没有描述符数量限制。&lt;/p&gt;

&lt;p&gt;epoll 对多线程编程更有友好，一个线程调用了 epoll_wait() 另一个线程关闭了同一个描述符也不会产生像 select 和 poll 的不确定情况。&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Create the epoll descriptor. Only one is needed per app, and is used to monitor all sockets.
// The function argument is ignored (it was not before, but now it is), so put your favorite number here
&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pollingfd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoll_create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xCAFE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pollingfd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;c1&quot;&gt;// report error
&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Initialize the epoll structure in case more members are added in future
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoll_event&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Associate the connection class instance with the event. You can associate anything
// you want, epoll does not use this information. We store a connection class pointer, pConnection1
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pConnection1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Monitor for input, and do not automatically rearm the descriptor after the event
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EPOLLIN&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EPOLLONESHOT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Add the descriptor into the monitoring list. We can do it even if another thread is
// waiting in epoll_wait - the descriptor will be properly added
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoll_ctl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epollfd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EPOLL_CTL_ADD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pConnection1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getSocket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// report error
&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Wait for up to 20 events (assuming we have added maybe 200 sockets before that it may happen)
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoll_event&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pevents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Wait for 10 seconds, and retrieve less than 20 epoll_event and store them into epoll_event array
&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ready&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoll_wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pollingfd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pevents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10000&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Check if epoll actually succeed
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// report error and abort
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// timeout; no event detected
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Check if any events detected
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pevents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EPOLLIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Get back our connection pointer
&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Connection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pevents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handleReadEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Copy&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clipboardErrorCopied&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;三种模式对比&quot;&gt;三种模式对比&lt;/h3&gt;

&lt;p&gt;epoll在select和poll（poll和select基本一样，有少量改进）的基础引入了eventpoll作为中间层，使用了先进的数据结构，是一种高效的多路复用技术。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;epoll只有在持有很多连接，并且每个连接都不是特别活跃的时候效率才高，其他的情况不见得比select好&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;==epoll只有在持有很多连接，并且每个连接都不是特别活跃的时候效率才高，其他的情况不见得比select好==&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww2.sinaimg.cn/large/006tNc79ly1g5ow9cexhhj30pv0c1tbv.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;应用场景&quot;&gt;&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Socket?id=%e5%ba%94%e7%94%a8%e5%9c%ba%e6%99%af&quot;&gt;应用场景&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;很容易产生一种错觉认为只要用 epoll 就可以了，select 和 poll 都已经过时了，其实它们都有各自的使用场景。&lt;/p&gt;

&lt;h4 id=&quot;1-select-应用场景&quot;&gt;&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Socket?id=_1-select-%e5%ba%94%e7%94%a8%e5%9c%ba%e6%99%af&quot;&gt;1. select 应用场景&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;select 的 timeout 参数精度为 1ns，而 poll 和 epoll 为 1ms，因此 select 更加适用于实时性要求比较高的场景，比如核反应堆的控制。&lt;/p&gt;

&lt;p&gt;select 可移植性更好，几乎被所有主流平台所支持。&lt;/p&gt;

&lt;h4 id=&quot;2-poll-应用场景&quot;&gt;&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Socket?id=_2-poll-%e5%ba%94%e7%94%a8%e5%9c%ba%e6%99%af&quot;&gt;2. poll 应用场景&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;poll 没有最大描述符数量的限制，如果平台支持并且对实时性要求不高，应该使用 poll 而不是 select。&lt;/p&gt;

&lt;h4 id=&quot;3-epoll-应用场景&quot;&gt;&lt;a href=&quot;https://cyc2018.github.io/CS-Notes/#/notes/Socket?id=_3-epoll-%e5%ba%94%e7%94%a8%e5%9c%ba%e6%99%af&quot;&gt;3. epoll 应用场景&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;只需要运行在 Linux 平台上，有大量的描述符需要同时轮询，并且这些连接最好是长连接。&lt;/p&gt;

&lt;p&gt;需要同时监控小于 1000 个描述符，就没有必要使用 epoll，因为这个应用场景下并不能体现 epoll 的优势。&lt;/p&gt;

&lt;p&gt;需要监控的描述符状态变化多，而且都是非常短暂的，也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中，造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用，频繁系统调用降低效率。并且 epoll 的描述符存储在内核，不容易调试。&lt;/p&gt;

&lt;h3 id=&quot;总结&quot;&gt;总结&lt;/h3&gt;

&lt;p&gt;多路复用往往在提升性能方面有着重要的作用。select系统调用的功能是对多个文件描述符进行监视，当有文件描述符的文件读写操作完成以及发生异常或者超时，该调用会返回这些文件描述符。select 需要遍历所有的文件描述符，就遍历操作而言，复杂度是 O(N)。&lt;/p&gt;

&lt;p&gt;epoll相关系统调用是在Linux 2.5 后的某个版本开始引入的。该系统调用针对传统的select/poll不足，设计上作了很大的改动。select/poll 的缺点在于:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;每次调用时要重复地从用户模式读入参数，并重复地扫描文件描述符。&lt;/li&gt;
  &lt;li&gt;每次在调用开始时，要把当前进程放入各个文件描述符的等待队列。在调用结束后，又把进程从各个等待队列中删除。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;epoll 是把 select/poll 单个的操作拆分为 1 个 epoll&lt;em&gt;create，多个 epoll&lt;/em&gt;ctrl和一个 wait。此外，操作系统内核针对 epoll 操作添加了一个文件系统，每一个或者多个要监视的文件描述符都有一个对应的inode 节点，主要信息保存在 eventpoll 结构中。而被监视的文件的重要信息则保存在 epitem 结构中，是一对多的关系。由于在执行 epoll&lt;em&gt;create 和 epoll&lt;/em&gt;ctrl 时，已经把用户模式的信息保存到内核了， 所以之后即便反复地调用 epoll_wait，也不会重复地拷贝参数，不会重复扫描文件描述符，也不反复地把当前进程放入/拿出等待队列。&lt;/p&gt;

&lt;p&gt;所以，当前主流的Server侧Socket实现大都采用了epoll的方式，例如Nginx， 在配置文件可以显式地看到 &lt;code class=&quot;highlighter-rouge&quot;&gt;use epoll&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://tech.meituan.com/2016/11/04/nio.html&quot;&gt;美团-javaNIO浅析&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html&amp;gt;&quot;&gt;深入分析 Java I/O 的工作机制&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/nightcurtis/article/details/51324105&quot;&gt;javaIO体系&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.jianshu.com/p/a4e03835921a&quot;&gt;跟着闪电侠学netty&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/Snailclimb/JavaGuide/blob/master/Java/BIO%2CNIO%2CAIO%20summary.md#12-%E4%BC%AA%E5%BC%82%E6%AD%A5-io&quot;&gt;JavaGuide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/29675083&quot;&gt;javaIO一本难念的经&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA&quot;&gt;老曹眼中的网络编程基础&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://segmentfault.com/a/1190000007240744&quot;&gt;高性能网络编程(二)：上一个10年，著名的C10K并发连接问题&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 06 Mar 2019 00:00:00 +0000</pubDate>
        <link>http://offery-gong.github.io/2019/03/06/JavaIO-NIO-AIO/</link>
        <guid isPermaLink="true">http://offery-gong.github.io/2019/03/06/JavaIO-NIO-AIO/</guid>
        
        <category>网络编程</category>
        
        
      </item>
    
      <item>
        <title>Tomcat顶层架构</title>
        <description>&lt;h3 id=&quot;tomcat顶层架构&quot;&gt;Tomcat顶层架构&lt;/h3&gt;

&lt;p&gt;了解了Tomcat的整体架构对以后深入了解Tomcat来说至关重要！&lt;/p&gt;

&lt;p&gt;先上一张Tomcat的顶层结构图（图A），如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ws2.sinaimg.cn/large/d8b81fbfly1g1fio4n4ryj20es0aajsa.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Tomcat中最顶层的容器是Server，代表着整个服务器，从上图中可以看出，一个Server可以包含至少一个Service，用于具体提供服务。&lt;/p&gt;

&lt;p&gt;Service主要包含两个部分：Connector和Container。从上图中可以看出 Tomcat 的心脏就是这两个组件，他们的作用如下：&lt;/p&gt;

&lt;p&gt;1、Connector用于处理连接相关的事情，并提供Socket与Request和Response相关的转化;&lt;/p&gt;

&lt;p&gt;2、Container用于封装和管理Servlet，以及具体处理Request请求；&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;一个Tomcat中只有一个Server，一个Server可以包含多个Service，一个Service只有一个Container，但是可以有多个Connectors，这是因为一个服务可以有多个连接，如同时提供Http和Https链接，&lt;/strong&gt;也可以提供向相同协议不同端口的连接,示意图如下（Engine、Host、Context下边会说到）：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://img-blog.csdn.net/20180108204347710?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGxnZW4xNTczODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;多个 Connector 和一个 Container 就形成了一个 Service，有了 Service 就可以对外提供服务了，但是 Service 还要一个生存的环境，必须要有人能够给她生命、掌握其生死大权，那就非 Server 莫属了&lt;/strong&gt;！所以整个 Tomcat 的生命周期由 Server 控制。&lt;/p&gt;

&lt;p&gt;另外，上述的包含关系或者说是父子关系，都可以在tomcat的conf目录下的&lt;code class=&quot;highlighter-rouge&quot;&gt;server.xml&lt;/code&gt;配置文件中看出，下图是删除了注释内容之后的一个完整的&lt;code class=&quot;highlighter-rouge&quot;&gt;server.xml&lt;/code&gt;配置文件（Tomcat版本为8.0）&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://img-blog.csdn.net/20180108194753633?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGxnZW4xNTczODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;详细的配置文件文件内容可以到Tomcat官网查看：&lt;/p&gt;

&lt;p&gt;http://tomcat.apache.org/tomcat-8.0-doc/index.html&lt;/p&gt;

&lt;p&gt;上边的配置文件，还可以通过下边的一张结构图更清楚的理解：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://wx4.sinaimg.cn/large/d8b81fbfly1g1fipdllxuj20lz0a4jrg.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Server标签设置的端口号为8005，shutdown=”SHUTDOWN” ，表示在8005端口监听“SHUTDOWN”命令，如果接收到了就会关闭Tomcat。一个Server有一个Service，当然还可以进行配置，一个Service有多个，Service左边的内容都属于Container的，Service下边是Connector。&lt;/p&gt;

&lt;h4 id=&quot;顶层架构小结&quot;&gt;顶层架构小结&lt;/h4&gt;

&lt;p&gt;（1）Tomcat中只有一个Server，一个Server可以有多个Service，一个Service可以有多个Connector和一个Container； 
（2） Server掌管着整个Tomcat的生死大权； 
（4）Service 是对外提供服务的； 
（5）Connector用于接受请求并将请求封装成Request和Response来具体处理； 
（6）Container用于封装和管理Servlet，以及具体处理request请求；&lt;/p&gt;

&lt;h3 id=&quot;connector和container的微妙关系&quot;&gt;&lt;strong&gt;Connector和Container的微妙关系&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;由上述内容我们大致可以知道一个请求发送到Tomcat之后，首先经过Service然后会交给我们的Connector，Connector用于接收请求并将接收的请求封装为Request和Response来具体处理，Request和Response封装完之后再交由Container进行处理，Container处理完请求之后再返回给Connector，最后在由Connector通过Socket将处理的结果返回给客户端，这样整个请求的就处理完了！&lt;/p&gt;

&lt;p&gt;Connector最底层使用的是Socket来进行连接的，Request和Response是按照HTTP协议来封装的，所以Connector同时需要实现TCP/IP协议和HTTP协议！&lt;/p&gt;

&lt;p&gt;Tomcat既然处理请求，那么肯定需要先接收到这个请求，接收请求这个东西我们首先就需要看一下Connector！&lt;/p&gt;

&lt;h3 id=&quot;connector架构分析&quot;&gt;&lt;strong&gt;Connector架构分析&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Connector用于接受请求并将请求封装成Request和Response，然后交给Container进行处理，Container处理完之后在交给Connector返回给客户端。&lt;/p&gt;

&lt;p&gt;因此，我们可以把Connector分为四个方面进行理解：&lt;/p&gt;

&lt;p&gt;（1）Connector如何接受请求的？&lt;/p&gt;

&lt;p&gt;（2）如何将请求封装成Request和Response的？&lt;/p&gt;

&lt;p&gt;（3）封装完之后的Request和Response如何交给Container进行处理的？&lt;/p&gt;

&lt;p&gt;（4）Container处理完之后如何交给Connector并返回给客户端的？&lt;/p&gt;

&lt;p&gt;首先看一下Connector的结构图（图B），如下所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ws3.sinaimg.cn/large/d8b81fbfly1g1fipxk82ij20lz0aj74i.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Connector就是使用ProtocolHandler来处理请求的，不同的ProtocolHandler代表不同的连接类型，比如：Http11Protocol使用的是普通Socket来连接的，Http11NioProtocol使用的是NioSocket来连接的。&lt;/p&gt;

&lt;p&gt;其中ProtocolHandler由包含了三个部件：Endpoint、Processor、Adapter。&lt;/p&gt;

&lt;p&gt;（1）Endpoint用来处理底层Socket的网络连接，Processor用于将Endpoint接收到的Socket封装成Request，Adapter用于将Request交给Container进行具体的处理。&lt;/p&gt;

&lt;p&gt;（2）Endpoint由于是处理底层的Socket网络连接，因此Endpoint是用来实现TCP/IP协议的，而Processor用来实现HTTP协议的，Adapter将请求适配到Servlet容器进行具体的处理。&lt;/p&gt;

&lt;p&gt;（3）Endpoint的抽象实现AbstractEndpoint里面定义的Acceptor和AsyncTimeout两个内部类和一个Handler接口。Acceptor用于监听请求，AsyncTimeout用于检查异步Request的超时，Handler用于处理接收到的Socket，在内部调用Processor进行处理。&lt;/p&gt;

&lt;p&gt;至此，我们应该很轻松的回答（1）（2）（3）的问题了，但是（4）还是不知道，那么我们就来看一下Container是如何进行处理的以及处理完之后是如何将处理完的结果返回给Connector的？&lt;/p&gt;

&lt;h3 id=&quot;container架构分析&quot;&gt;&lt;strong&gt;Container架构分析&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Container用于封装和管理Servlet，以及具体处理Request请求，在Connector内部包含了4个子容器，结构图如下（图C）：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ws1.sinaimg.cn/large/d8b81fbfly1g187s2pxctj20nj0eiq5i.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;4个子容器的作用分别是：&lt;/p&gt;

&lt;p&gt;（1）Engine：引擎，用来管理多个站点，一个Service最多只能有一个Engine；&lt;/p&gt;

&lt;p&gt;（2）Host：代表一个站点，也可以叫虚拟主机，通过配置Host就可以添加站点；&lt;/p&gt;

&lt;p&gt;（3）Context：代表一个应用程序，对应着平时开发的一套程序，或者一个WEB-INF目录以及下面的web.xml文件；&lt;/p&gt;

&lt;p&gt;（4）Wrapper：每一Wrapper封装着一个Servlet；&lt;/p&gt;

&lt;p&gt;下面找一个Tomcat的文件目录对照一下，如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://wx4.sinaimg.cn/large/d8b81fbfly1g187trse3mj20c00clgox.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Context和Host的区别是Context表示一个应用，我们的Tomcat中默认的配置下webapps下的每一个文件夹目录都是一个Context，其中ROOT目录中存放着主应用，其他目录存放着子应用，而整个webapps就是一个Host站点。&lt;/p&gt;

&lt;p&gt;我们访问应用Context的时候，如果是ROOT下的则直接使用域名就可以访问，例如：www.ledouit.com,如果是Host（webapps）下的其他应用，则可以使用www.ledouit.com/docs进行访问，当然默认指定的根应用（ROOT）是可以进行设定的，只不过Host站点下默认的主营用是ROOT目录下的。&lt;/p&gt;

&lt;p&gt;看到这里我们知道Container是什么，但是还是不知道Container是如何进行处理的以及处理完之后是如何将处理完的结果返回给Connector的？下边就开始探讨一下Container是如何进行处理的！&lt;/p&gt;

&lt;h3 id=&quot;container如何处理请求的&quot;&gt;&lt;strong&gt;Container如何处理请求的&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Container处理请求是使用Pipeline-Valve管道来处理的！（Valve是阀门之意）&lt;/p&gt;

&lt;p&gt;Pipeline-Valve是责任链模式，责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理，每个处理者负责做自己相应的处理，处理完之后将处理后的请求返回，再让下一个处理着继续处理。&lt;/p&gt;

&lt;p&gt;但是！Pipeline-Valve使用的责任链模式和普通的责任链模式有些不同！区别主要有以下两点：&lt;/p&gt;

&lt;p&gt;（1）每个Pipeline都有特定的Valve，而且是在管道的最后一个执行，这个Valve叫做BaseValve，BaseValve是不可删除的；&lt;/p&gt;

&lt;p&gt;（2）在上层容器的管道的BaseValve中会调用下层容器的管道。&lt;/p&gt;

&lt;p&gt;我们知道Container包含四个子容器，而这四个子容器对应的BaseValve分别在：StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve。&lt;/p&gt;

&lt;p&gt;Pipeline的处理流程图如下（图D）：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://wx2.sinaimg.cn/large/d8b81fbfly1g187yqbb84j20ly0fvq5i.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;（1）Connector在接收到请求后会首先调用最顶层容器的Pipeline来处理，这里的最顶层容器的Pipeline就是EnginePipeline（Engine的管道）；&lt;/p&gt;

&lt;p&gt;（2）在Engine的管道中依次会执行EngineValve1、EngineValve2等等，最后会执行StandardEngineValve，在StandardEngineValve中会调用Host管道，然后再依次执行Host的HostValve1、HostValve2等，最后在执行StandardHostValve，然后再依次调用Context的管道和Wrapper的管道，最后执行到StandardWrapperValve。&lt;/p&gt;

&lt;p&gt;（3）当执行到StandardWrapperValve的时候，会在StandardWrapperValve中创建FilterChain，并调用其doFilter方法来处理请求，这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet，其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法，这样请求就得到了处理！&lt;/p&gt;

&lt;p&gt;（4）当所有的Pipeline-Valve都执行完之后，并且处理完了具体的请求，这个时候就可以将返回的结果交给Connector了，Connector在通过Socket的方式将结果返回给客户端。&lt;/p&gt;
</description>
        <pubDate>Tue, 05 Mar 2019 00:00:00 +0000</pubDate>
        <link>http://offery-gong.github.io/2019/03/05/Tomcat%E9%A1%B6%E5%B1%82%E6%9E%B6%E6%9E%84/</link>
        <guid isPermaLink="true">http://offery-gong.github.io/2019/03/05/Tomcat%E9%A1%B6%E5%B1%82%E6%9E%B6%E6%9E%84/</guid>
        
        <category>Tomcat</category>
        
        
      </item>
    
      <item>
        <title>OLTP VS OLAP VS HTAP</title>
        <description>&lt;p&gt;OLTP是Online Transaction Processing的简称；OLAP是OnLine Analytical Processing的简称；HTAP是Hybrid Transactional/Analytical Processing的简称。Transaction是指形成一个逻辑单元，不可分割的一组读，写操作；Online一般指查询延迟在秒级或毫秒级，可以实现交互式查询。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OLTP的查询一般只会访问少量的记录，且大多时候都会利用索引&lt;/strong&gt;。在线的面向终端用户直接使用的Web应用：金融，博客，评论，电商等系统的查询都是OLTP查询，比如最常见的基于主键的CRUD操作。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OLAP的查询一般需要Scan大量数据，大多时候只访问部分列，聚合的需求（Sum，Count，Max，Min等）会多于明细的需求（查询原始的明细数据）&lt;/strong&gt;。 OLAP的典型查询一般像：现在各种应用在年末会发布的大数据分析和统计应用，比如2017豆瓣读书报告，2017豆瓣读书榜单，网易云音乐2017听歌报告； OLAP在企业中的一个重要应用就是BI分析，比如2017年最畅销的手机品牌Top5;哪类人群最喜欢小米或华为手机等等。&lt;/p&gt;

&lt;p&gt;《Designing-Data-Intensive-Applications》一书指出的OLTP和OLAP的主要区别如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://static.zybuluo.com/kangkaisen/ydf2bq899yi002i1hdcjz3o2/OLAP%20vs%20OLTP.png&quot; alt=&quot;OLAP vs OLTP&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在CMU-CS 15-415的课程中对OLAP和OLTP这样介绍：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://static.zybuluo.com/kangkaisen/crao9ltiib4x4uaf42b83hai/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202018-05-27%20%E4%B8%8B%E5%8D%889.14.36.png&quot; alt=&quot;OLTP&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://static.zybuluo.com/kangkaisen/3a21p0i9pun8wnvqgyzyvqlp/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202018-05-27%20%E4%B8%8B%E5%8D%889.14.43.png&quot; alt=&quot;OLAP&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在《An Overview of Data Warehousing and OLAP Technology》论文中对OLAP和OLTP这样介绍：&lt;/p&gt;

&lt;p&gt;OLTP的特点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;专门用来做日常的，基本的操作&lt;/li&gt;
  &lt;li&gt;任务由短的，原子的，隔离的事务组成&lt;/li&gt;
  &lt;li&gt;处理的数据量在G级别&lt;/li&gt;
  &lt;li&gt;重视一致性和可恢复性&lt;/li&gt;
  &lt;li&gt;事务的吞吐量是关键性能指标&lt;/li&gt;
  &lt;li&gt;最小化并发冲突&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OLAP的特点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;专门用来做决策支持&lt;/li&gt;
  &lt;li&gt;历史的，总结的，统一的数据比具体的，独立的数据更重要&lt;/li&gt;
  &lt;li&gt;侧重于查询&lt;/li&gt;
  &lt;li&gt;查询吞吐量和相应时间是关键性能指标&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;通过前面对OLTP和OLAP特点的介绍，大家对OLTP和OLAP应该有了感性的认识。&lt;/p&gt;

&lt;p&gt;最开始的时候，数据量比较小，所以数据库可以用来同时做OLTP和OLAP查询的，比如Mysql，如果数据比较小的话，Mysql的OLAP查询性能也是可以满足需求的。但是随着数据量越来越大，大概在1990年代，业界开始用单独的数据库来满足OLAP需求，并把这种专门满足OLAP需求的数据库称之为数据仓库（Data Warehousing）。这样做的原因是为了保证在线OLTP业务的稳定性，不让复杂的OLAP查询线上业务的稳定性和性能。&lt;/p&gt;

&lt;p&gt;Data Warehousing是所有决策支持技术的集合，目标是让行政管理人员，分析人员做出更好，更快的决策；Data Warehousing是主题相关的，完整的，时间变化的，稳定的数据集合，被用来作为组织制定决策的主要依据。Data Warehousing一般是公司各个OLTP系统数据的只读Copy， 数据一般会从各个OLTP数据库中抽取（Extract）出来，进行数据清理，并转换（Transform）为对OLAP更友好的数据格式，最终导入（Load）进Data Warehousing中。 这就是所谓的&lt;strong&gt;Extract–Transform–Load (ETL)&lt;/strong&gt;过程，下图是个示例。 目前业界使用最广泛的Data Warehousing应该都是基于Hive构建的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://static.zybuluo.com/kangkaisen/surr245wdrutmpjrta9kcfj4/ETL.png&quot; alt=&quot;ETL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Warehousing的数据模型主要有两类：星型模型和雪花模型。&lt;/strong&gt; 下图是张星型模型的示例，星型模型中的表分两类： fact table和 dimension tables。fact table的每一行记录特定时间发生的一条事件，fact table中的列一般分为两类：维度和指标。 维度一般表示一条事件的who, what, where, when, how, and why，指标一般是数值型的属性，比如价格，点击量，访问量等。 所谓的维表就是维度信息的详细描述，一般维表的主键和事实表中的外键进行关联。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://static.zybuluo.com/kangkaisen/ij2badyuu8qlwz7qx4cil533/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202018-05-27%20%E4%B8%8B%E5%8D%888.57.22.png&quot; alt=&quot;星型模型&quot; /&gt;&lt;/p&gt;

&lt;p&gt;所谓的雪花模型，就是将维表进一步拆为为子维表。如下图所示，Year维表和Month维表就是从Date维表中进一步拆分出来的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://static.zybuluo.com/kangkaisen/qcfa8oqbo54s3seaifvq5qi0/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202018-05-27%20%E4%B8%8B%E5%8D%889.05.05.png&quot; alt=&quot;屏幕快照 2018-05-27 下午9.05.05.png-121.3kB&quot; /&gt;&lt;/p&gt;

&lt;p&gt;星型模型和雪花模型名称的来源都是根据模型的样子命名的，星型模型是多个维表围着中间的事实表，比较像星星，雪花模型是看起来比较像雪花。&lt;strong&gt;一般在公司业务实际中使用更多的是星型模型，因为星型模型更加简单。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;前面简介了下OLTP和OLAP的特点，上面的特点决定了OLTP和OLAP在存储模型，查询计划的生成和优化上都是有很大区别。在&lt;a href=&quot;https://blog.bcmeng.com/post/tidb-application-htap.html#5-tidb-htap-%E6%BC%94%E8%BF%9B%E4%B9%8B%E8%B7%AF&quot;&gt;畅想TiDB应用场景和HTAP演进之路&lt;/a&gt; 一文中，解释了为什么&lt;strong&gt;OLTP系统需要行存，而OLAP系统需要列存&lt;/strong&gt;。（见文尾：） 正因为OLTP和OLAP在技术实现上的区别较大，所以在较长时间内，现在的数据库要么只满足OLTP需求，要么只满足OLAP需求。 但是这样做有以下缺点：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;数据需要存储多份&lt;/li&gt;
  &lt;li&gt;数据从OLTP系统进入OLAP系统会有延迟&lt;/li&gt;
  &lt;li&gt;OLTP系统和OLAP系统向外保留的查询接口可能不一致&lt;/li&gt;
  &lt;li&gt;需要同时维护多套系统&lt;/li&gt;
  &lt;li&gt;用户有额外的学习成本&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;正因为有以上问题，所以有人提出了HTAP的概念，即一个系统同时很好的满足OLTP和OLAP的需求。 但显然HTAP系统目前还是有很多挑战的，比如OLTP需要行存，OLAP需求列存，怎么同时满足行列两种存储需求？比如如何生成对OLTP和OLAP都最优的查询计划？ 比如如何保证OLTP和OLAP的查询不相互影响，如何进行资源隔离等等。在&lt;a href=&quot;https://blog.bcmeng.com/post/tidb-application-htap.html#5-tidb-htap-%E6%BC%94%E8%BF%9B%E4%B9%8B%E8%B7%AF&quot;&gt;畅想TiDB应用场景和HTAP演进之路&lt;/a&gt;中简单提了如何在存储上同时满足OLTP和OLAP。&lt;/p&gt;

&lt;p&gt;HTAP本质上和最初地一个单机数据库同时满足OLTP和OLAP一样，但问题是在大数据，分布式化的今天，一个系统要同时很好地满足OLTP和OLAP需求显然会难许多，但这些难题或许终将一个一个被解决。&lt;/p&gt;

&lt;h3 id=&quot;行存与列存优缺点与使用场景&quot;&gt;行存与列存优缺点与使用场景&lt;/h3&gt;

&lt;h4 id=&quot;行存的优缺点和适用场景&quot;&gt;行存的优缺点和适用场景&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;http://static.zybuluo.com/kangkaisen/mb734poudqs2yangznn3xd6x/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202018-04-30%20%E4%B8%8B%E5%8D%884.21.55.png&quot; alt=&quot;行存的优缺点和适用场景&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图是个行存的示意图，就是数据按行组织，查询时按行读取。行存在学术论文中一般简称为NSM（N-ary Storage Mode）。&lt;/p&gt;

&lt;p&gt;行存的优点如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;适合快速的点插入，点更新和点删除&lt;/li&gt;
  &lt;li&gt;对于需要访问全部列的查询十分友好&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;行存的缺点如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;对需要读取大量数据并访问部分列的场景不友好&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以行存适用于OLTP场景。&lt;/p&gt;

&lt;h4 id=&quot;列存的优缺点和适用场景&quot;&gt;列存的优缺点和适用场景&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;http://static.zybuluo.com/kangkaisen/szh3jebuhv0m9qxvzw7k7hcx/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202018-04-30%20%E4%B8%8B%E5%8D%884.33.50.png&quot; alt=&quot;列存的优缺点和适用场景&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图是个列存的示意图，就是数据按列组织，每列的数据连续存放在一起，查询时按列读取。 列存在学术论文中一般简称为DSM(Decomposition Storage Model)。&lt;/p&gt;

&lt;p&gt;列存的优点如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;只读取部分列时，可以减少IO&lt;/li&gt;
  &lt;li&gt;更好的编码和压缩（由于每列的数据类型相同）&lt;/li&gt;
  &lt;li&gt;更易于实现向量化执行&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;列存的缺点如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;不适合随机的插入，删除，更新。（多列之间存在拆分和合并的开销）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以列存适用于OLAP场景。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;参考资料：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1《Designing-Data-Intensive-Applications》第3章的第二部分《Transaction Processing or Analytics》&lt;/p&gt;

&lt;p&gt;2《An Overview of Data Warehousing and OLAPTechnology》论文&lt;/p&gt;

&lt;p&gt;3 2016年的CMU-CS 15-415课程的《Column Stores》小节&lt;/p&gt;
</description>
        <pubDate>Tue, 05 Mar 2019 00:00:00 +0000</pubDate>
        <link>http://offery-gong.github.io/2019/03/05/OLTP-VS-OLAP-VS-HTAP/</link>
        <guid isPermaLink="true">http://offery-gong.github.io/2019/03/05/OLTP-VS-OLAP-VS-HTAP/</guid>
        
        <category>大数据</category>
        
        
      </item>
    
      <item>
        <title>Hbase原理</title>
        <description>&lt;h2 id=&quot;hbase架构&quot;&gt;HBase架构&lt;/h2&gt;

&lt;h3 id=&quot;架构组成&quot;&gt;架构组成&lt;/h3&gt;

&lt;p&gt;只要记得在分布式的生产环境中，HBase 需要运行在 HDFS 之上，以 HDFS 作为其基础的存储设施。HBase 上层提供了访问的数据的 Java API 层，供应用访问存储在 HBase 的数据。在 HBase 的集群中主要由 Master 和 Region Server 组成，以及 Zookeeper，具体模块如下图所示。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;https://www.ibm.com/developerworks/cn/analytics/library/ba-cn-bigdata-hbase/image003.png&quot; height=&quot;400&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;https://blog-10039692.file.myqcloud.com/1506395765370_9254_1506395767893.png&quot; height=&quot;400&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;HBase 的集群是通过 Zookeeper 来进行机器之前的协调，也就是说 HBase Master 与 Region Server 之间的关系是依赖 Zookeeper 来维护。当一个 Client 需要访问 HBase 集群时，Client 需要先和 Zookeeper 来通信，然后才会找到对应的 Region Server。&lt;/li&gt;
  &lt;li&gt;每一个 Region Server 管理着很多个 Region。对于 HBase 来说，Region 是 HBase 并行化的基本单元。因此，数据也都存储在 Region 中。这里我们需要特别注意，&lt;strong&gt;每一个 Region  的 一个 Store 只存储一个 table 的 一个column family 的数据，并且是该 CF 中的一段（按 Row 的区间分成多个 Region）&lt;/strong&gt;。&lt;/li&gt;
  &lt;li&gt;Region 所能存储的数据大小是有上限的，当达到该上限时（Threshold），Region 会进行分裂，数据也会分裂到多个 Region 中，这样便可以提高数据的并行化，以及提高数据的容量。&lt;/li&gt;
  &lt;li&gt;每个 Region 包含着多个 Store 对象。每个 Store 包含一个 MemStore，和一个或多个 HFile。&lt;strong&gt;MemStore 便是数据在内存中的实体，并且一般都是有序的&lt;/strong&gt;。当数据向 Region 写入的时候，会先写入 MemStore。&lt;/li&gt;
  &lt;li&gt;当 MemStore 中的数据需要向底层文件系统倾倒（Dump）时（例如 MemStore 中的数据体积到达 MemStore 配置的最大值），Store 便会创建 StoreFile，而 StoreFile 就是对 HFile 一层封装。所以 MemStore 中的数据会最终写入到 HFile 中，也就是磁盘 IO。由于 HBase 底层依靠 HDFS，因此 HFile 都存储在 HDFS 之中。&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Master（table和region的管理工作）&lt;/p&gt;

    &lt;ol&gt;
      &lt;li&gt;负责RegionServer的负载均衡，调整region的分配&lt;/li&gt;
      &lt;li&gt;Region分裂后，为RegionServer分配新的Region&lt;/li&gt;
      &lt;li&gt;发现失效的region server并重新分配其上的region&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;HBase 允许多个 Master 节点共存&lt;/strong&gt;，但是这需要 Zookeeper 的帮助。不过当多个 Master 节点共存时，只有一个 Master 是提供服务的，其他的 Master 节点处于待命的状态。当正在工作的 Master 节点宕机时，其他的 Master 则会接管 HBase 的集群。&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Region Server&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;HRegionSserver维护Master分配给它的region&lt;/li&gt;
      &lt;li&gt;维护region的cache&lt;/li&gt;
      &lt;li&gt;处理region的flush、compact、split&lt;/li&gt;
      &lt;li&gt;内部管理一系列的HRegion对象&lt;/li&gt;
      &lt;li&gt;一个HRegionServer会有多个HRegion和一个HLog。&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;对于一个 Region Server 而言，其包括了多个 Region。Region Server 的作用只是管理表格，以及实现读写操作。Client 直接连接 Region Server，并通信获取 HBase 中的数据。对于 Region 而言，则是真实存放 HBase 数据的地方，也就说 &lt;strong&gt;Region 是 HBase 可用性和分布式的基本单位&lt;/strong&gt;。每个Region负责一小部分Rowkey范围的数据的读写和维护，Region包含了对应的起始行到结束行的所有信息。如果当一个表格很大，并由多个 CF 组成时，那么表的数据将存放在多个 Region 之间，并且在每个 Region 中会关联多个存储的单元（Store）。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Zookeeper&lt;/p&gt;

    &lt;ol&gt;
      &lt;li&gt;首先 Zookeeper 是作为 HBase Master 的 HA（高可用） 解决方案。&lt;strong&gt;Zookeeper 保证了至少有一个 HBase Master 处于运行状态&lt;/strong&gt;。&lt;/li&gt;
      &lt;li&gt;并且 Zookeeper 负责 Region 和 Region Server 的注册。通过Zoopkeeper来监控RegionServer的状态，当RegionSevrer有异常的时候，通过回调的形式通知Master RegionServer上下限的信息&lt;/li&gt;
      &lt;li&gt;存贮所有Region的寻址入口；&lt;/li&gt;
      &lt;li&gt;通过Zoopkeeper存储元数据的统一入口地址；存储Hbase的schema（元数据信息）。包括有哪些table、每个table有哪些column family等&lt;/li&gt;
      &lt;li&gt;其实 Zookeeper 发展到目前为止，已经成为了分布式大数据框架中容错性的标准框架。不光是 HBase，几乎所有的分布式大数据相关的开源框架，都依赖于 Zookeeper 实现 HA。&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;region-server的组成&quot;&gt;Region Server的组成&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;WAL（Hlog）：既Write Ahead Log。WAL是HDFS分布式文件系统中的一个文件。WAL用来存储尚未写入永久性存储区中的新数据。WAL也用来在服务器发生故障时进行数据恢复。&lt;/li&gt;
  &lt;li&gt;Block Cache：Block cache是读缓存。Block cache将经常被读的数据存储在内存中来提高读取数据的效率。当Block cache的空间被占满后，其中被读取频率最低的数据将会被杀出。&lt;/li&gt;
  &lt;li&gt;MemStore：MemStore是写缓存。其中存储了从WAL中写入但尚未写入硬盘的数据。MemStore中的数据在写入硬盘之前会先进行排序操作。每一个region中的每一个column family对应一个MemStore。&lt;/li&gt;
  &lt;li&gt;Hfiles：Hfiles存在于硬盘上，根据排序号的键存储数据行。&lt;/li&gt;
  &lt;li&gt;&lt;img src=&quot;https://mapr.com/blog/in-depth-look-hbase-architecture/assets/blogimages/HBaseArchitecture-Blog-Fig8.png&quot; alt=&quot;img&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;region组成&quot;&gt;Region组成&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;每一个HRegion又由很多的Store组成，每一个Store存储的实际上是一个列簇（ColumnFamily）下所有的数据&lt;/strong&gt;。此外，在每一个Store（又名HStore）中有包含一块MemStore。MemStore驻留在内存中，数据到来时首先更新到MemStore中，当到达阈值之后再flush（默认64M）到对应的StoreFile（又名HFile）中，所以每一个Store包含多个StoreFile，StoreFile负责的是实际数据存储，为HBase中最小的存储单元。&lt;/li&gt;
  &lt;li&gt;达到某个阈值时，分裂（默认256M）。所以一个HRegionServer管理多个表，一个表下有多个Region，一个HRegion有多少个列族就有多少个Store,Store下有多个StoreFile文件，是HBase中最小的存储单元&lt;/li&gt;
  &lt;li&gt;以Region为单位管理, region(startKey,endKey)；【默认情况下，刚创建一个表时，并不知道startkey和endkey】
每个Column Family单独存储：storeFile；（ storefile的数量一多（到达阀值），就合并（同时合并版本以及删除之前要删除的数据）；合并后大小到达阀值就split）&lt;/li&gt;
  &lt;li&gt;当某个Column Family累积的大小（具体的数据量） &amp;gt; 某阈值时，自动分裂成两个Region；合并之后，旧数据也不是立即删除，而是复制一份并同内存中的数据一起写到磁盘，在之后，LSM-Tree会提供一些机制来回收这些空间。[4]&lt;/li&gt;
  &lt;li&gt;如何找到某行属于哪个region呢？.META.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;hfile-的结构&quot;&gt;HFile 的结构&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://www.ibm.com/developerworks/cn/analytics/library/ba-cn-bigdata-hbase/image004.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;从图中我们可以看到 HFile 由很多个数据块（Block）组成，并且有一个固定的结尾块。其中的数据块是由一个 Header 和多个 Key-Value 的键值对组成。在结尾的数据块中包含了数据相关的索引信息，系统也是通过结尾的索引信息找到 HFile 中的数据。HFile 中的数据块大小默认为 64KB。如果访问 HBase 数据库的场景多为有序的访问，那么建议将该值设置的大一些。如果场景多为随机访问，那么建议将该值设置的小一些。一般情况下，通过调整该值可以提高 HBase 的性能。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HFile中包含了一个多层索引系统。这个多层索引是的HBase可以在不读取整个文件的情况下查找数据。这一多层索引类似于一个B+树。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://img-blog.csdn.net/20170607154559135?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWWFva2FpX0Fzc3VsdE1hc3Rlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast&quot; alt=&quot;HFile structure&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;hlog保证数据可靠性&quot;&gt;HLog（保证数据可靠性）&lt;/h3&gt;

&lt;p&gt;Hlog是Hbase实现&lt;strong&gt;WAL（Write ahead log）方式&lt;/strong&gt;产生的日志信息，保证事务一致性。&lt;strong&gt;内部是一个简单的顺序日志&lt;/strong&gt;。每个RegionServer对应1个Hlog(备注：1.x版本的可以开启MultiWAL功能，允许多个Hlog)，所有对于该RegionServer的写入都被记录到Hlog中。Hlog实现的功能就是我们前面讲到的保证数据安全。&lt;/p&gt;

&lt;p&gt;当RegionServer出现问题的时候，能跟进Hlog来做数据恢复。此外为了保证恢复的效率，Hbase会限制最大保存的Hlog数量，如果达到Hlog的最大个数（hase.regionserver.max.logs参数控制）的时候，就会触发强制刷盘操作。对于已经刷盘的数据，其对应的Hlog会有一个&lt;strong&gt;过期&lt;/strong&gt;的概念，Hlog过期后，会被监控线程移动到 .oldlogs，然后会被自动删除掉。&lt;/p&gt;

&lt;h4 id=&quot;hlog数据结构&quot;&gt;Hlog数据结构&lt;/h4&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;https://blog-10039692.file.myqcloud.com/1506396165989_1943_1506396168304.png&quot; height=&quot;300&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;从上图我们可以看出都个Region共享一个Hlog文件，单个Region在Hlog中是按照时间顺序存储的，但是多个Region可能并不是完全按照时间顺序。&lt;/p&gt;

&lt;p&gt;每个Hlog最小单元由Hlogkey和WALEdit两部分组成。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Hlogkey由sequenceid、timestamp、cluster ids、regionname以及tablename等组成，&lt;/li&gt;
  &lt;li&gt;WALEdit是由一系列的KeyValue组成，对一行上所有列（即所有KeyValue）的更新操作，都包含在同一个WALEdit对象中，这主要是为了实现写入一行多个列时的原子性。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注意，图中有个&lt;strong&gt;sequenceid&lt;/strong&gt;的东东。sequenceid是一个store级别的自增序列号，这东东非常重要，region的数据恢复和Hlog过期清除都要依赖这个东东。下面就来简单描述一下sequenceid的相关逻辑。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Memstore在达到一定的条件会触发刷盘的操作，刷盘的时候会获取刷新到最新的一个sequenceid的下一个sequenceid，并将新的sequenceid赋给oldestUnflushedSequenceId，并刷到Ffile中。有点绕，举个例子来说明：比如对于某一个store，开始的时候oldestUnflushedSequenceId为NULL，此时，如果触发flush的操作，假设初始刷盘到sequenceid为10，那么hbase会在10的基础上append一个空的Entry到HLog，最新的sequenceid为11，然后将sequenceid为11的号赋给oldestUnflushedSequenceId，并将oldestUnflushedSequenceId的值刷到Hfile文件中进行持久化。&lt;/li&gt;
  &lt;li&gt;Hlog文件对应所有Region的store中最大的sequenceid如果已经刷盘，就认为Hlog文件已经过期，就会移动到.oldlogs，等待被移除。&lt;/li&gt;
  &lt;li&gt;当RegionServer出现故障的时候，需要对Hlog进行回放来恢复数据。回放的时候会读取Hfile的oldestUnflushedSequenceId中的sequenceid和Hlog中的sequenceid进行比较，小于sequenceid的就直接忽略，但与或者等于的就进行重做。回放完成后，就完成了数据的恢复工作。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;hlog生命周期&quot;&gt;Hlog生命周期&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;产生&lt;/strong&gt; 所有涉及到数据的变更都会先写Hlog，除非是你关闭了Hlog&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;滚动&lt;/strong&gt; Hlog的大小通过参数hbase.regionserver.logroll.period控制，默认是1个小时，时间达到hbase.regionserver.logroll.period 设置的时间，Hbase会创建一个新的Hlog文件。这就实现了Hlog滚动的目的。Hbase通过hbase.regionserver.maxlogs参数控制Hlog的个数。滚动的目的，为了控制单个Hlog文件过大的情况，方便后续的过期和删除。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;过期&lt;/strong&gt; 前面我们有讲到sequenceid这个东东，Hlog的过期依赖于对sequenceid的判断。Hbase会将Hlog的sequenceid和Hfile最大的sequenceid（刷新到的最新位置）进行比较，如果该Hlog文件中的sequenceid比刷新的最新位置的sequenceid都要小，那么这个Hlog就过期了，过期了以后，对应Hlog会被移动到.oldlogs目录。 这里有个问题，&lt;strong&gt;为什么要将过期的Hlog移动到.oldlogs目录，而不是直接删除呢？ 答案是因为Hbase还有一个主从同步的功能，这个依赖Hlog来同步Hbase的变更，有一种情况不能删除Hlog，那就是Hlog虽然过期，但是对应的Hlog并没有同步完成&lt;/strong&gt;，因此比较好的做好是移动到别的目录。再增加对应的检查和保留时间。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;删除&lt;/strong&gt; 如果Hbase开启了replication，当replication执行完一个Hlog的时候，会删除Zoopkeeper上的对应Hlog节点。在Hlog被移动到.oldlogs目录后，Hbase每隔hbase.master.cleaner.interval（默认60秒）时间会去检查.oldlogs目录下的所有Hlog，确认对应的Zookeeper的Hlog节点是否被删除，如果Zookeeper 上不存在对应的Hlog节点，那么就直接删除对应的Hlog。 hbase.master.logcleaner.ttl（默认10分钟）这个参数设置Hlog在.oldlogs目录保留的最长时间。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;hbase中表结构逻辑视图&quot;&gt;Hbase中表结构逻辑视图&lt;/h2&gt;

&lt;p&gt;在 HBase 中首先会有 Column Family 的概念，简称为 CF。&lt;strong&gt;表 3. 数据在 HBase 中的排布（逻辑上）&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Row-Key&lt;/th&gt;
      &lt;th&gt;Value（CF、Qualifier、Version）&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;info{‘姓’: ‘张’，’名’:’三’} pwd{‘密码’: ‘111’}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;Info{‘姓’: ‘李’，’名’:’四’} pwd{‘密码’: ‘222’}&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;表中两个 CF，分别是 info 和 pwd。info 存储着姓名相关列的数据，而 pwd 则是密码相关的数据。CF 一般用于将相关的列（Column）组合起来。在物理上 HBase 其实是按 CF 存储的，只是按照 Row-key 将相关 CF 中的列关联起来。&lt;strong&gt;物理上的数据排布&lt;/strong&gt;大致可以如表 4 所示。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Row-Key&lt;/th&gt;
      &lt;th&gt;CF:Column-Key&lt;/th&gt;
      &lt;th&gt;时间戳&lt;/th&gt;
      &lt;th&gt;Cell Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;info:fn&lt;/td&gt;
      &lt;td&gt;123456789&lt;/td&gt;
      &lt;td&gt;三&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;info:ln&lt;/td&gt;
      &lt;td&gt;123456789&lt;/td&gt;
      &lt;td&gt;张&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;info:fn&lt;/td&gt;
      &lt;td&gt;123456789&lt;/td&gt;
      &lt;td&gt;四&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;info:ln&lt;/td&gt;
      &lt;td&gt;123456789&lt;/td&gt;
      &lt;td&gt;李&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;上表中的 fn 和 ln 称之为 Column-key 或者 Qulifimer。在 Hbase 中，&lt;strong&gt;Row-key 加上 CF 加上 Qulifier 再加上一个时间戳&lt;/strong&gt;才可以定位到一个单元格数据（Hbase 中每个单元格默认有 3 个时间戳的版本数据）&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;https://www.ibm.com/developerworks/cn/analytics/library/ba-cn-bigdata-hbase/image001.png&quot; height=&quot;300&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://gitee.com/chenssy/blog-home/raw/master/image/series-images/bigData/hbase/1228818-20180328185514080-1540820263.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;总结&quot;&gt;总结&lt;/h3&gt;

&lt;p&gt;HBase 就是一个有序的多维 Map，其中每一个 Row-key 映射了许多数据，这些数据存储在 CF 中的 Column。我们可以用下图来表示这句话。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;https://www.ibm.com/developerworks/cn/analytics/library/ba-cn-bigdata-hbase/image005.png&quot; height=&quot;300&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;rowkey设计原则&quot;&gt;RowKey设计原则&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Rowkey长度原则：Rowkey 是一个二进制码流，Rowkey 的长度被很多开发者建议说设计在10~100 个字节，不过建议是越短越好，不要超过16 个字节。
原因如下：
（1）数据的持久化文件HFile 中是按照KeyValue 存储的，如果Rowkey 过长比如100 个
字节，1000 万列数据光Rowkey 就要占用100*1000 万=10 亿个字节，将近1G 数据，这会极
大影响HFile 的存储效率；
（2）MemStore 将缓存部分数据到内存，如果Rowkey 字段过长内存的有效利用率会降
低，系统将无法缓存更多的数据，这会降低检索效率。因此Rowkey 的字节长度越短越好。
（3）目前操作系统是都是64 位系统，内存8 字节对齐。控制在16 个字节，8 字节的
整数倍利用操作系统的最佳特性。&lt;/li&gt;
  &lt;li&gt;Rowkey散列原则
如果Rowkey 是按时间戳的方式递增，不要将时间放在二进制码的前面，建议将Rowkey
的高位作为散列字段，由程序循环生成，低位放时间字段，这样将提高数据均衡分布在每个
Regionserver 实现负载均衡的几率。如果没有散列字段，首字段直接是时间信息将产生所有
新数据都在一个 RegionServer 上堆积的热点现象，这样在做数据检索的时候负载将会集中
在个别RegionServer，降低查询效率。&lt;/li&gt;
  &lt;li&gt;Rowkey唯一原则&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;hbase工作流程&quot;&gt;Hbase工作流程&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://gitee.com/chenssy/blog-home/raw/master/image/series-images/bigData/hbase/1228818-20180402130346713-706113248.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;hbase的写流程&quot;&gt;Hbase的写流程&lt;/h3&gt;

&lt;p&gt;1、Table 中的所有行都按照 RowKsey 的字典序排列。&lt;/p&gt;

&lt;p&gt;2、Table 在行的方向上分割为多个 HRegion。&lt;/p&gt;

&lt;p&gt;3、HRegion 按大小分割的(默认 10G)，每个表一开始只有一个 HRegion，随着数据不断插入 表，HRegion 不断增大，当增大到一个阀值的时候，HRegion 就会等分会两个新的 HRegion。 当表中的行不断增多，就会有越来越多的 HRegion。&lt;/p&gt;

&lt;p&gt;4、HRegion 是 Hbase 中分布式存储和负载均衡的最小单元。最小单元就表示不同的 HRegion 可以分布在不同的 HRegionserver 上。但一个 HRegion 是不会拆分到多个 server 上的。&lt;/p&gt;

&lt;p&gt;5、HRegion 虽然是负载均衡的最小单元，但并不是物理存储的最小单元。事实上，HRegion 由一个或者多个 Store 组成，每个 Store 保存一个 Column Family。每个 Strore 又由一个 memStore 和 0 至多个 StoreFile 组成&lt;/p&gt;

&lt;p&gt;Hbase的写入流程如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://blog-10039692.file.myqcloud.com/1506396036453_6524_1506396038477.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;第1步：Client获取数据写入的Region所在的RegionServer&lt;/p&gt;

&lt;p&gt;第2步：请求写Hlog&lt;/p&gt;

&lt;p&gt;第3步：请求写MemStore。只有当写Hlog和写MemStore都成功了才算请求写入完成。Memstore存在于内存中，其中存储的是按键排好序的&lt;strong&gt;待写入硬盘的数据&lt;/strong&gt;。数据也是按键排好序写入HFile中的。&lt;strong&gt;每一个Region中的每一个Column family对应一个Memstore文件&lt;/strong&gt;。因此对数据的更新也是对应于每一个Column family。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mapr.com/blog/in-depth-look-hbase-architecture/assets/blogimages/HBaseArchitecture-Blog-Fig11.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;第4步：MemStore后续会逐渐刷到HDFS中。当MemStore中积累了足够多的数据之后，整个Memcache中的数据会被一次性写入到HDFS里的一个新的HFile中。&lt;strong&gt;因此HDFS中一个Column family可能对应多个HFile&lt;/strong&gt;。这个HFile中包含了相应的cell，或者说键值的实例。这些文件随着MemStore中积累的对数据的操作被flush到硬盘上而创建。&lt;/p&gt;

&lt;p&gt;因为MemStore中的数据已经按照键排好序，所以这是一个顺序写的过程。由于顺序写操作避免了磁盘大量寻址的过程，所以这一操作非常高效。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mapr.com/blog/in-depth-look-hbase-architecture/assets/blogimages/HBaseArchitecture-Blog-Fig13.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;备注：Hlog存储在HDFS，当RegionServer出现异常，需要使用Hlog来恢复数据。&lt;/p&gt;

&lt;h3 id=&quot;region寻址方式&quot;&gt;Region寻址方式&lt;/h3&gt;

&lt;p&gt;那么client要对某一行数据做读写的时候如何能知道具体要去访问哪个RegionServer呢？&lt;/p&gt;

&lt;p&gt;旧的：&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;https://blog-10039692.file.myqcloud.com/1506395970742_8984_1506395972814.png&quot; height=&quot;350&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;新的：&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;https://blog-10039692.file.myqcloud.com/1506396002537_2157_1506396004590.png&quot; height=&quot;300&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;第1步：Client请求ZK获取.META.所在的RegionServer的地址。&lt;/p&gt;

&lt;p&gt;第2步：Client请求.META.所在的RegionServer获取访问数据所在的RegionServer地址，client会将.META.的相关信息cache下来，以便下一次快速访问。&lt;/p&gt;

&lt;p&gt;第3步：Client请求数据所在的RegionServer，获取所需要的数据。&lt;/p&gt;

&lt;p&gt;总结去掉-ROOT-的原因有如下2点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;其一：提高性能&lt;/li&gt;
  &lt;li&gt;其二：2层结构已经足以满足集群的需求&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;hbase的meta-table&quot;&gt;HBase的META table&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;META table中保存了HBase中所有region的信息。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;META table的格式类似于B tree。&lt;/strong&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;META table的结构如下：&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;键：region的起始键，region id。&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;值：Region server&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://mapr.com/blog/in-depth-look-hbase-architecture/assets/blogimages/HBaseArchitecture-Blog-Fig7.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;client缓存问题&quot;&gt;Client缓存问题&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Client会缓存.META.的数据，用来加快访问&lt;/strong&gt;，既然有缓存，那它什么时候更新？如果.META.更新了，比如Region1不在RerverServer2上了，被转移到了RerverServer3上。client的缓存没有更新会有什么情况？
其实，&lt;strong&gt;Client的元数据缓存不更新，当.META.的数据发生更新&lt;/strong&gt;。如上面的例子，由于Region1的位置发生了变化，Client再次根据缓存去访问的时候，会出现错误，当出现异常达到重试次数后就会去.META.所在的RegionServer获取最新的数据，如果.META.所在的RegionServer也变了，Client就会去ZK上获取.META.所在的RegionServer的最新地址。&lt;/p&gt;

&lt;h2 id=&quot;hbase的compaction&quot;&gt;HBase的Compaction&lt;/h2&gt;

&lt;p&gt;大量HFile的产生，会消耗更多的文件句柄，同时会造成RS在数据查询等的效率大幅度下降，HBase为解决这个问题，引入了compact操作，RS通过compact把大量小的HFile进行文件合并，生成大的HFile文件。&lt;/p&gt;

&lt;p&gt;RS上的compact根据功能的不同，可以分为两种不同类型，即：minor compact和major compact。&lt;/p&gt;

&lt;h3 id=&quot;minor-compact&quot;&gt;minor compact&lt;/h3&gt;

&lt;p&gt;HBase会自动选取一些较小的HFile进行合并，并将结果写入几个较大的HFile中。这一过程称为Minor compaction。Minor compaction通过Merge sort的形式将较小的文件合并为较大的文件，从而减少了存储的HFile的数量，提升HBase的性能。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mapr.com/blog/in-depth-look-hbase-architecture/assets/blogimages/HBaseArchitecture-Blog-Fig18.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;major-compaction&quot;&gt;Major Compaction&lt;/h3&gt;

&lt;p&gt;所谓Major Compaction指的是HBase将对应于&lt;strong&gt;某一个Column family&lt;/strong&gt;的所有HFile重新整理并合并为一个HFile，&lt;strong&gt;并在这一过程中删除已经删除或过期的cell，更新现有cell的值&lt;/strong&gt;。这一操作大大提升读的效率。但是因为Major compaction需要重新整理所有的HFile并写入一个HFile，这一过程包含大量的硬盘I/O操作以及网络数据通信。这一过程也称为写放大（Write amplification）。&lt;strong&gt;在Major compaction进行的过程中，当前Region基本是处于不可访问的状态&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mapr.com/blog/in-depth-look-hbase-architecture/assets/blogimages/HBaseArchitecture-Blog-Fig19.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;region拆分&quot;&gt;Region拆分&lt;/h2&gt;

&lt;p&gt;compact将多个HFile合并单个HFile文件，随着数据量的不断写入，单个HFile也会越来越大，大量小的HFile会影响数据查询性 能，大的HFile也会，HFile越大，相对的在HFile中搜索的指定rowkey的数据花的时间也就越长，HBase同样提供了region的 split方案来解决大的HFile造成数据查询时间过长问题。&lt;/p&gt;

&lt;p&gt;每一个表格最初都对应于一个region。随着region&lt;a href=&quot;https://www.baidu.com/s?wd=%E4%B8%AD%E6%95%B0%E6%8D%AE&amp;amp;tn=24004469_oem_dg&amp;amp;rsv_dl=gh_pl_sl_csd&quot;&gt;中数据&lt;/a&gt;量的增加，region会被分割成两个子region。每一个子region中存储原来一半的数据。同时Region server会通知HMaster这一分割。出于&lt;strong&gt;负载均衡的原因，HMaster可能会将新产生的region分配给其他的Region server管理&lt;/strong&gt;（这也就导致了Region server服务远端数据的情况的产生）。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mapr.com/blog/in-depth-look-hbase-architecture/assets/blogimages/HBaseArchitecture-Blog-Fig21.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;hbase故障恢复&quot;&gt;Hbase故障恢复&lt;/h2&gt;

&lt;h2 id=&quot;ps分布式系统cap&quot;&gt;PS.分布式系统CAP&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://www.wangbase.com/blogimg/asset/201807/bg2018071607.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Partition tolerance，中文叫做”分区容错”。   大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区（partition）。分区容错的意思是，区间通信可能失败。比如，一台服务器放在中国，另一台服务器放在美国，这就是两个区，它们之间可能无法通信。&lt;/li&gt;
  &lt;li&gt;Consistency Consistency 中文叫做”一致性”。意思是，写操作之后的读操作，必须返回该值&lt;/li&gt;
  &lt;li&gt;Availability 中文叫做”可用性”，意思是只要收到用户的请求，服务器就必须给出回应&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;面试题目&quot;&gt;面试题目&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Hbase特点是什么？&lt;/strong&gt;&lt;/p&gt;

    &lt;ol&gt;
      &lt;li&gt;表由行（Row）以及列（Column）组成，行由row key和一个或多个列及其值组成；&lt;/li&gt;
      &lt;li&gt;列必须属于某一列族（Column family），一个列族可以有一各或多个列（&lt;strong&gt;一列由列簇和列修饰符组成&lt;/strong&gt;，他们通常由冒号（：） 分隔），其&lt;strong&gt;在存储架构中就是一个Hfile&lt;/strong&gt;。&lt;/li&gt;
      &lt;li&gt;列中的数据可以是稀疏的，空值并不占用存储空间。&lt;/li&gt;
      &lt;li&gt;数据按主键排序，同时表按主键划分为多个Region。底层是LSM树（Long-Structed Merge Tree）。&lt;/li&gt;
      &lt;li&gt;&lt;img src=&quot;https://img-blog.csdn.net/20180311093934996?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSGFpeFdhbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70&quot; alt=&quot;è¿éåå¾çæè¿°&quot; /&gt;&lt;/li&gt;
      &lt;li&gt;缺点：
        &lt;ol&gt;
          &lt;li&gt;单一RowKey固有的局限性决定了它不可能有效地支持多条件查询[2]&lt;/li&gt;
          &lt;li&gt;不适合于大范围扫描查询&lt;/li&gt;
          &lt;li&gt;不直接支持 SQL 的语句查询&lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;应用场景？&lt;/strong&gt;OLAP&lt;/p&gt;

    &lt;ol&gt;
      &lt;li&gt;半结构化或非结构化数据: 
对于数据结构字段不够确定或杂乱无章非常难按一个概念去进行抽取的数据适合用HBase，因为HBase支持动态添加列。&lt;/li&gt;
      &lt;li&gt;记录很稀疏： 
RDBMS的行有多少列是固定的。为null的列浪费了存储空间。而如上文提到的，HBase为null的Column不会被存储，这样既节省了空间又提高了读性能。&lt;/li&gt;
      &lt;li&gt;多版本号数据： 
依据Row key和Column key定位到的Value能够有随意数量的版本号值，因此对于须要存储变动历史记录的数据，用HBase是很方便的。比方某个用户的Address变更，用户的Address变更记录也许也是具有研究意义的。&lt;/li&gt;
      &lt;li&gt;仅要求最终一致性： 
对于数据存储事务的要求不像金融行业和财务系统这么高，只要保证最终一致性就行。（比如HBase+elasticsearch时，可能出现数据不一致）&lt;/li&gt;
      &lt;li&gt;高可用和海量数据以及很大的瞬间写入量： WAL解决高可用，支持PB级数据，put性能高&lt;/li&gt;
      &lt;li&gt;索引插入比查询操作更频繁的情况。比如，对于历史记录表和日志文件。（HBase的写操作更加高效）&lt;/li&gt;
      &lt;li&gt;业务场景简单： 不需要太多的关系型数据库特性，列入交叉列，交叉表，事务，连接等。&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Hbase读写流程&lt;/strong&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Hbase切分流程&lt;/strong&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;zk，Hmaster，Rs等的作用？&lt;/strong&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;HBASE中compact用途是什么，什么时候触发，分为哪两种,有什么区别，有哪些相关配置参数？&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;在hbase中每当有memstore数据flush到磁盘之后，就形成一个storefile，当storeFile的数量达到一定程度后，就需要将 storefile 文件来进行 compaction 操作。
Compact 的作用：
1&amp;gt;.&lt;strong&gt;合并文件&lt;/strong&gt;
2&amp;gt;.&lt;strong&gt;清除过期，多余版本的数据&lt;/strong&gt;
3&amp;gt;.提高读写数据的效率
HBase 中实现了两种 compaction 的方式：minor and major. 这两种 compaction 方式的区别是：&lt;/p&gt;

    &lt;p&gt;1、Minor 操作只用来做部分文件的合并操作以及包括 minVersion=0 并且设置 ttl 的过期版本清理，不做任何删除数据、多版本数据的清理工作。
2、Major 操作是对 Region 相同列族的HStore下的所有StoreFile执行合并操作，最终的结果是整理合并出一个文件。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;关系型数据库和非关系型数据库的区别？Hbase和RDBMS的区别&lt;/strong&gt;&lt;/p&gt;

    &lt;ol&gt;
      &lt;li&gt;hbase是按照列族存储的；普通RDB是一列&lt;/li&gt;
      &lt;li&gt;数据类型：没有数据类型，都是字节数组（有一个工具类Bytes，将java对象序列化为字节数组）。
数据操作：HBase只有很简单的插入、查询、删除、清空等操作，表和表之间是分离的，没有复杂的表和表之间的关系，而传统数据库通常有各式各样的函数和连接操作。
存储模式：Hbase适合于非结构化数据存储，基于列存储而不是行。
数据维护：HBase的更新操作不应该叫更新，它实际上是插入了新的数据，而传统数据库是替换修改
时间版本：Hbase数据写入cell时，还会附带时间戳，默认为数据写入时RegionServer的时间，但是也可以指定一个不同的时间。数据可以有多个版本。
可伸缩性：Hbase这类分布式数据库就是为了这个目的而开发出来的，所以它能够轻松增加或减少硬件的数量，并且对错误的兼容性比较高。而传统数据库通常需要增加中间层才能实现类似的功能&lt;/li&gt;
      &lt;li&gt;&lt;img src=&quot;https://blog-10039692.file.myqcloud.com/1506395449042_5248_1506395451085.png&quot; alt=&quot;img&quot; /&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;hbase支持哪些查找方式？&lt;/strong&gt;&lt;/p&gt;

    &lt;ol&gt;
      &lt;li&gt;基于Rowkey的单行查询&lt;/li&gt;
      &lt;li&gt;基于Rowkey的范围扫描&lt;/li&gt;
      &lt;li&gt;全表扫描&lt;/li&gt;
    &lt;/ol&gt;

    &lt;p&gt;因此，Rowkey对Hbase的性能影响非常大，Rowkey的设计就显得尤为的重要。设计的时候要兼顾基于Rowkey的单行查询也要键入Rowkey的范围扫描。具体Rowkey要如何设计后续会整理相关的文章做进一步的描述。这里大家只要有一个概念就是Rowkey的设计极为重要。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Hbase和Hive区别&lt;/strong&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;为什么Hbase中要定义列族？&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;在HBase中，数据是按Column Family来分割的，同一个Column Family下的所有列的数据放在一个文件（为简化下面的描述在此使用文件这个词，在HBase内部使用的是Store）中。
HBase本身的设计目标是支持稀疏表，而稀疏表通常会有很多列，但是每一行有值的列又比较少。
如果不使用Column Family的概念，那么有两种设计方案：
1.把所有列的数据放在一个文件中（也就是传统的按行存储）。那么当我们想要访问少数几个列的数据时，需要遍历每一行，读取整个表的数据，这样子是很低效的。
2.把每个列的数据单独分开存在一个文件中（按列存储）。那么当我们想要访问少数几个列的数据时，只需要读取对应的文件，不用读取整个表的数据，读取效率很高。然而，由于稀疏表通常会有很多列，这会导致文件数量特别多，这本身会影响文件系统的效率。
而Column Family的提出就是为了在上面两种方案中做一个折中。&lt;strong&gt;HBase中将一个Column Family中的列存在一起，而不同Column Family的数据则分开。&lt;/strong&gt;
由于在HBase中Column Family的数量通常很小，同时HBase建议把经常一起访问的比较类似的列放在同一个Column Family中，这样就可以在访问少数几个列时，只读取尽量少的数据。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Hbase列族数据库的理解。&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Hbase的存储方式就决定了列数据库！！
1、&lt;strong&gt;存储方式&lt;/strong&gt;：在HBase中，&lt;strong&gt;Key-Value&lt;/strong&gt;是最小的存储单元。每一个Key-Value对应一个列，Value对应于一个列的列值。&lt;/p&gt;

    &lt;p&gt;2、&lt;strong&gt;Rowkey作用&lt;/strong&gt;：而且HBase是&lt;strong&gt;根据Rowkey来进行检索&lt;/strong&gt;的（&lt;strong&gt;索引&lt;/strong&gt;），系统通过找到某个Rowkey所在的Region，然后将查询数据的请求路由到该Region获取数据。
所以&lt;strong&gt;设计Rowkey&lt;/strong&gt;就决定了未来你的查询性能&lt;/p&gt;

    &lt;p&gt;3、hbase为什么叫列式存储？&lt;/p&gt;

    &lt;p&gt;因为&lt;strong&gt;hbase列的可以动态增加&lt;/strong&gt;，并且列为空就不存储数据,节省存储空间。&lt;/p&gt;

    &lt;p&gt;举个例子吧，如下：&lt;/p&gt;

    &lt;p&gt;1）Mysql结构化数据：你要指定一个表user，你必须&lt;/p&gt;

    &lt;p&gt;预先定义好各个字段，姓名、年龄、ID。后期要扩展新字段麻烦&lt;/p&gt;

    &lt;p&gt;2）Hbase列式存储：你只要设计好rowKey，定义好列簇，至于里面什么属性，有多少字段类型，&lt;/p&gt;

    &lt;p&gt;无需预先定义好，扩展性极佳，需要的列字段可以不停的增加。&lt;/p&gt;

    &lt;p&gt;也许前期你就姓名、年龄、ID三个字段，未来你随时可以存储第四个字段，第五个字段，第n个字段到你的列簇里，因为同一个列簇中的所有数据都存储在一个文件里。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;HBase 中每张表的列族个数建议设在1~3之间。&lt;strong&gt;其实，HBase 支持的列族个数并没有限制&lt;/strong&gt;，但为什么文档建议在1~3之间呢？&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://www.iteblog.com/archives/2474.html&quot;&gt;https://www.iteblog.com/archives/2474.html&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;参考文献&quot;&gt;参考文献&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://cloud.tencent.com/developer/article/1006043&quot;&gt;Hbase技术细节笔记&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://lxw1234.com/archives/2016/09/719.htm&quot;&gt;lxwHbase设计原理&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.ibm.com/developerworks/cn/analytics/library/ba-cn-bigdata-hbase/index.html&quot;&gt;Hbase深入浅出&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/Yaokai_AssultMaster/article/details/72877127#t1&quot;&gt;翻译-深入理解Hbase系统&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/HaixWang/article/details/79514886&quot;&gt;面试题&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 02 Mar 2019 00:00:00 +0000</pubDate>
        <link>http://offery-gong.github.io/2019/03/02/Hbase/</link>
        <guid isPermaLink="true">http://offery-gong.github.io/2019/03/02/Hbase/</guid>
        
        <category>数据库</category>
        
        <category>Hbase</category>
        
        
      </item>
    
      <item>
        <title>阿里HBase实践总结（高可用，高一致性）</title>
        <description>&lt;h3 id=&quot;概述&quot;&gt;&lt;strong&gt;概述&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;HBase是一个开源的非关系型分布式数据库（NoSQL）,基于谷歌的BigTable建模，是一个高可靠性、高性能、高伸缩的分布式存储系统，使用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。&lt;/p&gt;

&lt;p&gt;HBase最初是以Hadoop子项目的形式进行开发建设，直到2010年5月才正式成为Apache的顶级项目独立发展。伴随着互联网时代数据的澎湃增长，HBase作为基础存储系统得到了快速发展与应用，大批知名商业公司(Facebook、Yahoo、阿里等)不自主地加入到了HBase生态建设队伍，成为Apache最活跃的社区之一。&lt;/p&gt;

&lt;p&gt;HBase的能力特点，可以简单概括为下表，基于这些能力，其被广泛应用于海量结构化数据在线访问、大数据实时计算、大对象存储等领域。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://yqfile.alicdn.com/2a92240a20ac84995fb457136a4067dff36ede22.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;阿里从2011年初开始步入HBase的发展、建设之路，是国内最早应用、研究、发展、回馈的团队，也诞生了HBase社区在国内的第一位Committer，成为HBase在中国发展的积极布道者。过去的几年时间，阿里累积向社区回馈了上百个Patch, 在诸多核心模块的功能、稳定性、性能作出积极重大的贡献，拥有多位Committer，成为推动HBase的长远发展的重要力量之一。&lt;/p&gt;

&lt;p&gt;阿里是一家综合生态型公司，内部庞大业务矩阵高速发展，在基础存储方面，需要更好的功能灵活性、基础设施适应性、服务稳定性、效率成本。&lt;/p&gt;

&lt;p&gt;因此，阿里HBase团队发展维护了HBase的内部分支，其基于阿里巴巴/蚂蚁金服的环境和业务需求，对社区HBase进行深度定制与改进，从软件系统、解决方案、稳定护航、发展支撑等全方位提供一站式大数据基础存储服务。&lt;/p&gt;

&lt;h3 id=&quot;hbase在阿里的使用&quot;&gt;&lt;strong&gt;HBase在阿里的使用&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Ali-HBase作为阿里巴巴技术大厦的基础存储设施，全面服务于淘宝、天猫、蚂蚁金服、菜鸟、阿里云、高德、优酷等各个领域，满足业务对于大数据分布式存储的基本需求。&lt;/p&gt;

&lt;p&gt;在刚刚过去的2016年双11，HBase承载访问量达到了上百GB/秒(写入)与上百GB/秒(读取)，相当于全国人民一秒收发一条短信，在业务记录、安全风控、实时计算、日志监控、消息聊天等多个场景发挥重要价值。面对如此规模的业务体量，阿里巴巴团队对于如何基于HBase打造稳定、高效、易用的存储服务，形成了一套完善的产品体系与实践经验，其整体大图如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://yqfile.alicdn.com/3df7ad4a775be4d9fdb8bf18453b2dd5e2a6b1f8.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;总体上，我们以定制的软件内核为中心，建设质量平台、运维平台、业务平台和数据流设施四大内容，以支持业务对于基础数据服务的全方位需求。&lt;/p&gt;

&lt;p&gt;接下来，本文会围绕可用性、数据流、性能优化等方面介绍最近的一些具体工作，希望能够给相关领域的同学带来一点帮助。&lt;/p&gt;

&lt;h3 id=&quot;高可用建设&quot;&gt;&lt;strong&gt;高可用建设&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;服务持续可用是互联网系统的显著特征，但由于物理环境、软件Bug的不确定性，要做到系统的高可用往往不是一件容易的事，尤其是对于有状态的存储系统而言。今天，我们统一使用SLA(服务等级协议)去衡量一个分布式系统的可用性，比如SLA达到99.99%的系统，其全年的不可用时间小于52.6分钟；99.999%的系统，其全年的不可用时间小于5.25分钟，达到这个能力的系统一般可以称之为高可用。&lt;/p&gt;

&lt;p&gt;面对断电、断网、硬件故障等物理机房的不可靠性，任何一个高可用系统必须通过双机房，甚至多机房部署的方式进行容灾。对于存储系统，这就要求数据能够在机房间冗余复制，并保证各个机房的数据对上层应用的一致性。所以，高可用建设是我们过去很长时间的重要工作。&lt;/p&gt;

&lt;h3 id=&quot;集群异步复制&quot;&gt;&lt;strong&gt;集群异步复制&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Apache HBase从0.92版本开始支持&lt;strong&gt;Replication功能，它会实时地、异步地将一个HBase集群中的增量数据复制(推送方式)到另一个HBase集群，当主集群故障不可用时，应用可以切换访问到备集群，从而实现数据与服务的机房容灾。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;下面的篇幅，将主要介绍阿里在使用Replication过程中的经验与改进，期望能和在类似场景工作的同学有所共鸣。&lt;/p&gt;

&lt;h4 id=&quot;复制效率&quot;&gt;&lt;strong&gt;复制效率&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;由于在线业务的可用性要求，阿里HBase很早便开始使用Replication功能去部署双机房容灾，迎之而来的第一个大问题是数据复制的效率，尤其异地远距离部署（比如上海与深圳跨城复制）时更加严重，表现为数据复制的吞吐小于客户端写入主集群的吞吐，数据不断积压，延迟逐渐增大，只能等待凌晨低峰期逐渐消化。我们对此进行深入分析，着重优化了以下几点，才得以保障跨城集群复制也能稳定保持在秒级内。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;提升源端发送效率&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HBase Replication的基本数据复制过程是源端串行读取HLog的内容，发送到目标端机器，由目标端解析HLog并写入数据写。我们发现，因为源端的串行读取、发送HLog，当集群写入吞吐大的时候，会存在严重的性能瓶颈，为此，我们重构了这一块逻辑，将HLog的读取与发送解耦，并且发送由单线程优化为多线程，使得整体的源端发送能力大幅提升。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;提升目标端Sink效率&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在Replication的默认实现中，源端会按照HLog的原始写入顺序进行回放。为了提升目标端的写入效率，我们将所有待发送的HLog先进行排序，使得同表同Region的数据都能合并处理，同时将目标端的数据写入尽量并行化。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;热点辅助&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;尽管做了以上两点后，集群间的数据复制能力大大增强，但是个别服务器仍然会由于负载过大，而产生一定的复制延迟。从本质上来说，这是因为HBase的服务器分配了更多的资源服务于来自客户端的写入请求，当某个服务器成为集群中的写入热点并高负载工作时，这个节点的数据复制基本很难再消化庞大的写吞吐。这是一个曾困扰我们很久的问题，你可以用一些运维的方式去解决。比如开启更多的线程数，但这并不能总有效。因为服务于客户端的线程数，要远远大于Replication的线程数。再比如从热点服务器移走Region，降低吞吐与负载，但热点并不保证是恒定的，可能会跳跃在各个服务器，我们也开发了新的基于历史监控的负载均衡算法，以尽可能地让请求均衡。&lt;/p&gt;

&lt;p&gt;很多时候，通过运维管理手段能够控制影响、化解问题，但当你需要维护上百个集群时，一点一滴的运维要求慢慢堆积成很高的壁垒。所以，我们尝试改进系统能力，用自动、一劳永逸地方式去解决热点下的数据复制积压问题。面对热点的基本思路是散列，在这个具体场景上，我们打破原先的自生产自推送的设计，利用整个集群的能力，使得热点服务器上积压的数据(HLog文件)，能够由集群中的其他空闲服务器进行消化。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;配置在线调整&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;配置的在线调整不仅能极大提升运维幸福感，而且对于系统改进可以产生更加敏捷的反馈。这并不新鲜，但这是一项十分重要的能力，我们在系统改进的道路上也对其特别重视。HBase的Replication功能会有很多参数，我们将其全部优化为可在线调整，给日常的服务支撑带来了很大的价值。&lt;/p&gt;

&lt;h4 id=&quot;多链路&quot;&gt;&lt;strong&gt;多链路&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;业务多地多单元部署是阿里技术架构的一项重要特征，这要求基础存储具备数据链路的灵活流动性。今天，阿里HBase会在多地部署多集群，集群间数据相互流动，以满足单元化业务的需求。&lt;/p&gt;

&lt;p&gt;在支持数据多链路的生产应用上，我们总结了以下几个要点。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;表级别链路&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当一个HBase集群启用多个数据链路后，我们期望自由设置表的数据可以被复制到其中的一个或多个链路，使得整个数据的流动更加灵活。为此，我们增加了一种特性，通过设置表的属性，以决定该表的数据流向哪些链路，使得整个数据流动图可以由业务架构师任意设计，十分灵活。此外，当需要在集群间热迁移数据时，它也能带来十分重大的作用。 整体效果如下，以表为单位数据可以任意流动：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://yqfile.alicdn.com/429f620829d269fba43550a39dac4bd8573a10ff.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;链路可视&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当数据可以在多个集群任意流动后，一个很迫切的需求是链路拓扑以及复制状况的可视。为此，我们强化了Replication的信息层，不仅源端保留它到多个目标的链路信息，而且每个目标端也会保留多个源端到它的链路信息，从而我们可以从任意一个集群绘制整个链路拓扑图。同时，我们极大丰富Replication的运行状况信息，并将之汇聚到HBase的Master节点，由其统一汇总展现，从中我们可以清晰得到数据是否积压、复制的性能瓶颈、节点间的均衡情况、具体的延迟时间等信息，其中复制的延迟时间是一个十分关键的信息。基本信息如图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://yqfile.alicdn.com/4081946f562ab41ec1793cee47e3c014422b8a73.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;循环复制&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在数据多链路下，会产生一些循环复制的场景。比如集群A-&amp;gt;B-&amp;gt;C-&amp;gt;A，这是一个简单的链接式复制，当数据流过某个集群时，HBase Replication会在数据中添加该集群ID的信息，以防止同一条数据被多次流经同一个集群，基于这个设计，即使复制链路存在环，数据也不会产生无限循环流动。但是，仍然有一个效率问题不得不提，对于A&amp;lt;-&amp;gt;B&amp;lt;-&amp;gt;C&amp;lt;-&amp;gt;A这样一个数据链路，我们发现客户端写入到A集群的数据，在B集群和C集群上会被复制写入两次，一次通过A-&amp;gt;B链路写入，另一次通过A-&amp;gt;C-&amp;gt;B链路写入。所以，为了避免这种写入放大，需要在链路部署上防止产生这种环。在过去实践的一些场景，发现这种环状链路不得不存在，所以系统层面，我们也对Replication做了相关优化，以去除这种写入放大。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;链路隔离&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当源集群配置了多个数据链路后，我们总是期望这些链路之间相互隔离，不会因为一个链路的积压影响其他链路。在大多数时候，这一切都如预期工作，但当集群故障时，糟糕的事情发生了，我们发现一个异常链路会阻塞全部链路的复制恢复，究其原因，是因为在数据复制的恢复期间，很多资源是所有链路共享的。所以，这些资源的链路解耦成为我们的工作，同时，也好好对数据复制的宕机恢复速度进行了优化。&lt;/p&gt;

&lt;h3 id=&quot;数据的一致性&quot;&gt;&lt;strong&gt;数据的一致性&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;今天，大多数生产系统会使用异步方式去实现集群间的数据复制，因为这样效率更高、逻辑更清晰。这意味着，&lt;strong&gt;集群间数据是最终一致模型，当流量从主切换到备，从备上无法访问完整的数据，因为复制存在滞后，并且当主集群永久不可恢复，数据也会存在部分丢失。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;为了满足业务场景的&lt;strong&gt;强一致需求&lt;/strong&gt;，我们采用了两种方式。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第一种，异步复制下的强一致切换&lt;/strong&gt;。虽然备集群的数据集滞后于主集群，但是在主集群网络健康的情况下，仍然可以保障切换前后数据的强一致。其基本过程如下，首先让主集群禁止数据写入，然后等待主集群的数据全部复制备集群，切换流量到备集群。这里存在两个依赖，一个是集群的写入控制功能(支持禁止来自客户端的数据写入)，另一个是复制延迟的确定性，虽然数据是异步复制的，但是我们将数据的复制时间点明确化，即该时间点之前写入的数据已经完全复制到了备集群。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二种，数据复制使用同步的方式&lt;/strong&gt;。即当数据写入返回客户端成功后，能保证数据在主备集群均已写入，从而即使主集群完全不可恢复，数据在备集群中也能保证完整。&lt;/p&gt;

&lt;p&gt;为了满足类似场景的需求，阿里HBase研发了同步方式的集群间数据复制，具体内容可参考下一节。&lt;/p&gt;

&lt;h4 id=&quot;冗余与成本&quot;&gt;&lt;strong&gt;冗余与成本&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;数据在集群间的冗余复制，给系统的可用性带来了数量级的提高，但同时也意味着更大的成本开销，在保证可用性下如何优化成本是一个需要重点思考的问题，阿里HBase在这方面投入了较大精力的尝试，具体内容将在接下来的”性能与成本”章节进行介绍。&lt;/p&gt;

&lt;h4 id=&quot;集群同步复制&quot;&gt;&lt;strong&gt;集群同步复制&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;上文提到，HBase集群可以使用异步方式的数据复制来构建双机房容灾，当主集群故障不能提供服务时，就会切换请求到备集群，保障系统整体高可用。然而，异步复制模式下存在的问题是：在服务切换后，由于主备集群间的数据并非强一致，存在部分数据无法通过备集群获取或者访问到的内容过旧。也就是说，如果应用对于数据访问具有强一致要求，现有的异步复制设计，无法在主集群故障时，仍然保证系统的高可用。&lt;/p&gt;

&lt;p&gt;为此，阿里HBase团队投入研发集群同步复制功能，使得主集群不可用时，备集群的数据能达到和主集群完全一致，业务可以无感知的切换到备集群。相比于异步复制，同步复制会带来的额外的开销，但整个写入吞吐/性能的影响，在我们的设计中，做到了尽量的相近。其整体功能点如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;数据强一致性保证。数据写入主备集群，主集群不可用后，备集群可以恢复所有在主集群写入成功的数据&lt;/li&gt;
  &lt;li&gt;高性能。主备集群HLog写入采用异步并行的方式写入，对写入性能影响微弱&lt;/li&gt;
  &lt;li&gt;列族级粒度。列族级别的配置，支持同集群下同个表的不同列簇可以使用不同的复制方式，同步或异步。&lt;/li&gt;
  &lt;li&gt;同异步复制共存。任何情况下，同步复制表的任何操作不会影响异步表的读写。&lt;/li&gt;
  &lt;li&gt;灵活切换。备集群不可用，同步复制可以一键切换为异步复制，不阻塞主集群写入。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;关于数据的强一致，我们进行了如下定义：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;返回应用成功，则一定主备都写成功&lt;/li&gt;
  &lt;li&gt;返回应用错误，则未决(主备是否成功不能确定)&lt;/li&gt;
  &lt;li&gt;数据一旦读取成功，则主备永远均可读，不会出现主读成功切换至备后读不到或者备读得到主读不到的情况&lt;/li&gt;
  &lt;li&gt;任何情况下，保证主备集群的最终一致性&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们遵从简单、高效的原则去设计同步复制功能，简单意味着该功能与原核心逻辑保持最大程度的隔离，能够快速达到生产稳定性要求，并能很好地降级成异步复制；高效意味着主备必须并行写，这在错误处理上增加了不少的难度。&lt;strong&gt;整体实现方案如下：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;客户端向主集群写入数据的时候，会并行写入两份Log，一份是本地HLog文件，另一份是备集群的HLog文件，我们称之为RemoteLog.两者皆成功，才返回客户端成功。&lt;/li&gt;
  &lt;li&gt;RemoteLog仅在故障切换后，用以回放数据。正常运行时，不做任何使用，备集群的数据仍然通过现有的异步复制链路写入。同时，可以通过停写RemoteLog，把同步复制降级成异步复制。&lt;/li&gt;
  &lt;li&gt;HBase数据的多版本特性，使得基于HLog的操作回放具有幕等性，所以，在故障切换后，RemoteLog中的数据回放会存在一定的重复，但不会影响数据正确性。&lt;/li&gt;
  &lt;li&gt;主备集群存在Active和Standby状态，只有Active状态的集群才能接受客户端的数据写入&lt;/li&gt;
  &lt;li&gt;在备集群切换为Active状态之前，会对RemoteLog全局上锁，从而防止客户端写入数据到主集群返回成功。这也意味着，主备集群在任何时刻，只有一个处于Active状态，不会有脑裂发生。&lt;/li&gt;
  &lt;li&gt;RemoteLog会定期由主集群清理，主集群服务器的一个HLog文件对应一个或多个RemoteLog，所以当主集群的HLog文件中的数据被完全复制到备集群后，相应的RemoteLog就可以被删除。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其基本结构如图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://yqfile.alicdn.com/b6d7db1867a7f4d8b6a340f407c0af541988094a.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在这里，主备角色是不对等的，我们通过部署进行分配。其中，主-&amp;gt;备使用同步复制模式，一旦流量切换到备后，备-&amp;gt;主使用异步复制模式。&lt;/p&gt;

&lt;p&gt;由于主备双Log的并发写入，使得同步复制的性能能够与异步复制接近，在实际使用中，我们观察到客户端写入响应时间增加小于10%。最后，我们列举一些&lt;strong&gt;应用同步复制容灾的场景&lt;/strong&gt;，以供大家参考。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;基于状态变更数据的场景。HBase中提供了CheckAndMutate接口，用以支持条件写入/更新/删除，其含义是当某一条件达成时，才执行该写操作。这意味着查询到的数据必须是强一致的，不然就会写入错误的数据。比如，对于一笔交易记录，其状态只能从“已付款”变更为“已发货”，而不能从其他状态变更为“已发货”，所以在数据更新时需要做状态的条件判断。&lt;/li&gt;
  &lt;li&gt;日志/消息的顺序订阅。对于日志/消息产品而言，订阅数据的完整性是其最核心的保证，也就是说通过HBase进行Scan的时候，必须保证能扫描到范围内的每一行数据。如果切换后，主备数据存在不一致，则会出现scan过程中跳过某些数据，造成订阅少数据。&lt;/li&gt;
  &lt;li&gt;流计算。由于流计算不停地基于中间结果和新的数据流进行迭代处理，作为存储中间结果的数据库，必须时刻具备数据的强一致，才能保证数据计算结果的正确性。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;总结&quot;&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;集群间的数据复制是HBase用来构建机房容灾、提供高可用性的重要武器，阿里HBase通常使用异步复制方式部署，着重改进其在复制效率、多链路、一致性等方面的能力。同时，也研发了一种高效的同步复制方式，以满足数据强一致场景的容灾需求。&lt;/p&gt;

&lt;h3 id=&quot;挑战&quot;&gt;挑战&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;GC的挑战&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HBase作为JAVA性存储系统，大容量的内存堆使得YoungGC、FullGC的停顿成为我们一直以来挥之不去的痛苦。探究GC的原理机制，我们明确&lt;strong&gt;HBase内部的写缓冲Memstore和读缓存BlockCache是造成GC停顿的最大源头&lt;/strong&gt;，正在尝试用全新研发的完全自管理内存的Map以替换JDK自带的Map，从而消除GC的影响。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;SQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们正在尝试提供SQL方式访问HBase。它会增加数据类型，降低用户的开发理解门槛，促进异构系统之间的数据流动效率；它会增加全局二级索引，使得多条件查询更加高效；它会简化查询表达，使得性能优化更加普及；它会增加通用的热点解决方案，帮助用户免去复杂的散列逻辑。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;容器部署&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们正在尝试将HBase部署运行于Docker之上，使得整体运维更加敏捷，集群伸缩更加自如，资源使用更加充分。&lt;/p&gt;
</description>
        <pubDate>Fri, 01 Mar 2019 00:00:00 +0000</pubDate>
        <link>http://offery-gong.github.io/2019/03/01/%E9%98%BF%E9%87%8CHBase%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93-%E9%AB%98%E5%8F%AF%E7%94%A8-%E9%AB%98%E4%B8%80%E8%87%B4%E6%80%A7/</link>
        <guid isPermaLink="true">http://offery-gong.github.io/2019/03/01/%E9%98%BF%E9%87%8CHBase%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93-%E9%AB%98%E5%8F%AF%E7%94%A8-%E9%AB%98%E4%B8%80%E8%87%B4%E6%80%A7/</guid>
        
        <category>Hbase</category>
        
        
      </item>
    
      <item>
        <title>JVM调优总结</title>
        <description>&lt;h2 id=&quot;jvm调优&quot;&gt;JVM调优&lt;/h2&gt;

&lt;h3 id=&quot;1-常用工具&quot;&gt;1. 常用工具&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&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;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;jps&lt;/td&gt;
      &lt;td&gt;显示指定系统内所有的hotspot虚拟机进程&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;jstat&lt;/td&gt;
      &lt;td&gt;收集Hotspot虚拟机各方面运行时数据&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;jinfo&lt;/td&gt;
      &lt;td&gt;显示JVM配置信息&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;jmap&lt;/td&gt;
      &lt;td&gt;生成指定进程的堆转储快照（heapdump）&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;jhat&lt;/td&gt;
      &lt;td&gt;分析heapdump快照&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;jstack&lt;/td&gt;
      &lt;td&gt;虚拟机线程快照（stack trace）：即jvm中每一条编程正在执行的方法堆栈集合。&lt;/td&gt;
      &lt;td&gt;定位线程出现长时间停顿的原因：线程间死锁、死循环、请求外部资源时间过长等&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Jconsole&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;VisualVM&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;2收集器选择及jvm参数配置&quot;&gt;2.收集器选择及JVM参数配置&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;垃圾收集器选择 ：Serial可以直接排除掉，现在最普通的服务器也有双核64位\8G内存，默认的收集器是PS Scavenge和PS MarkSweep。所以在并行(Parallel)和并发(Concurrent)两者之间选择。如果系统对峰值处理要求不高，而对一两秒的停顿可以接受，则使用(-XX:+UseParallelGC)；如果应用对响应有更高的要求，停顿最好小于一秒，则使用(-XX:+UseConcMarkSweepGC)。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;JVM配置方面，一般情况可以先用默认配置（基本的一些初始参数可以保证一般的应用跑的比较稳定了），在测试中根据系统运行状况（会话并发情况、会话时间等），结合gc日志、内存监控、使用的垃圾收集器等进行合理的调整，当老年代内存过小时可能引起频繁Full GC，当内存过大时Full GC时间会特别长。&lt;/p&gt;

    &lt;p&gt;那么JVM的配置比如新生代、老年代应该配置多大最合适呢？答案是不一定，调优就是找答案的过程，物理内存一定的情况下，新生代设置越大，老年代就越小，Full GC频率就越高，但Full GC时间越短；相反新生代设置越小，老年代就越大，Full GC频率就越低，但每次Full GC消耗的时间越大。建议如下：&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;-Xms和-Xmx的值设置成相等，堆大小默认为-Xms指定的大小，默认空闲堆内存小于40%时，JVM会扩大堆到-Xmx指定的大小；空闲堆内存大于70%时，JVM会减小堆到-Xms指定的大小。如果在Full GC后满足不了内存需求会动态调整，这个阶段比较耗费资源。&lt;/li&gt;
      &lt;li&gt;新生代尽量设置大一些，让对象在新生代多存活一段时间，每次Minor GC 都要尽可能多的收集垃圾对象，防止或延迟对象进入老年代的机会，以减少应用程序发生Full GC的频率。
老年代如果使用CMS收集器，新生代可以不用太大，因为CMS的并行收集速度也很快，收集过程比较耗时的并发标记和并发清除阶段都可以与用户线程并发执行。&lt;/li&gt;
      &lt;li&gt;方法区大小的设置，1.6之前的需要考虑系统运行时动态增加的常量、静态变量等，1.7只要差不多能装下启动时和后期动态加载的类信息就行。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;代码实现方面，性能出现问题比如程序等待、内存泄漏除了JVM配置可能存在问题，代码实现上也有很大关系：&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;避免创建过大的对象及数组：过大的对象或数组在新生代没有足够空间容纳时会直接进入老年代，如果是短命的大对象，会提前出发Full GC。&lt;/li&gt;
      &lt;li&gt;避免同时加载大量数据，如一次从数据库中取出大量数据，或者一次从Excel中读取大量记录，可以分批读取，用完尽快清空引用。&lt;/li&gt;
      &lt;li&gt;当集合中有对象的引用，这些对象使用完之后要尽快把集合中的引用清空，这些无用对象尽快回收避免进入老年代。&lt;/li&gt;
      &lt;li&gt;可以在合适的场景（如实现缓存）采用软引用、弱引用，比如用软引用来为ObjectA分配实例：SoftReference objectA=new SoftReference(); 在发生内存溢出前，会将objectA列入回收范围进行二次回收，如果这次回收还没有足够内存，才会抛出内存溢出的异常。
避免产生死循环，产生死循环后，循环体内可能重复产生大量实例，导致内存空间被迅速占满。&lt;/li&gt;
      &lt;li&gt;尽量避免长时间等待外部资源（数据库、网络、设备资源等）的情况，缩小对象的生命周期，避免进入老年代，如果不能及时返回结果可以适当采用异步处理的方式等。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;3jvm问题排查记录案例&quot;&gt;3.JVM问题排查记录案例&lt;/h3&gt;

&lt;p&gt;JVM服务问题排查 https://blog.csdn.net/jacin1/article/details/44837595&lt;/p&gt;

&lt;p&gt;次让人难以忘怀的排查频繁Full GC过程 http://caogen81.iteye.com/blog/1513345&lt;/p&gt;

&lt;p&gt;线上FullGC频繁的排查 https://blog.csdn.net/wilsonpeng3/article/details/70064336/&lt;/p&gt;

&lt;p&gt;【JVM】线上应用故障排查 https://www.cnblogs.com/Dhouse/p/7839810.html&lt;/p&gt;

&lt;p&gt;一次JVM中FullGC问题排查过程 http://iamzhongyong.iteye.com/blog/1830265&lt;/p&gt;

&lt;p&gt;JVM内存溢出导致的CPU过高问题排查案例 https://blog.csdn.net/nielinqi520/article/details/78455614&lt;/p&gt;

&lt;p&gt;一个java内存泄漏的排查案例 https://blog.csdn.net/aasgis6u/article/details/54928744&lt;/p&gt;

&lt;h3 id=&quot;4-常用jvm参数参考&quot;&gt;4. 常用JVM参数参考：&lt;/h3&gt;
&lt;p&gt;| 参数                    | 说明                                                         | 实例                     |
| ———————– | ———————————————————— | ———————— |
| -Xms                    | 初始堆大小，默认物理内存的1/64                               | -Xms512M                 |
| -Xmx                    | 最大堆大小，默认物理内存的1/4                                | -Xms2G                   |
| -Xmn                    | 新生代内存大小，官方推荐为整个堆的3/8                        | -Xmn512M                 |
| -Xss                    | 线程堆栈大小，jdk1.5及之后默认1M，之前默认256k               | -Xss512k                 |
| -XX:NewRatio=n          | 设置新生代和年老代的比值。如:为3，表示年轻代与年老代比值为1：3，年轻代占整个年轻代年老代和的1/4 | -XX:NewRatio=3           |
| -XX:SurvivorRatio=n     | 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:8，表示Eden：Survivor=8:1:1，一个Survivor区占整个年轻代的1/8 | -XX:SurvivorRatio=8      |
| -XX:PermSize=n          | 永久代初始值，默认为物理内存的1/64                           | -XX:PermSize=128M        |
| -XX:MaxPermSize=n       | 永久代最大值，默认为物理内存的1/4                            | -XX:MaxPermSize=256M     |
| -verbose:class          | 在控制台打印类加载信息                                       |                          |
| -verbose:gc             | 在控制台打印垃圾回收日志                                     |                          |
| -XX:+PrintGC            | 打印GC日志，内容简单                                         |                          |
| -XX:+PrintGCDetails     | 打印GC日志，内容详细                                         |                          |
| -XX:+PrintGCDateStamps  | 在GC日志中添加时间戳                                         |                          |
| -Xloggc:filename        | 指定gc日志路径                                               | -Xloggc:/data/jvm/gc.log |
| -XX:+UseSerialGC        | 年轻代设置串行收集器Serial                                   |                          |
| -XX:+UseParallelGC      | 年轻代设置并行收集器Parallel Scavenge                        |                          |
| -XX:ParallelGCThreads=n | 设置Parallel Scavenge收集时使用的CPU数。并行收集线程数。     | -XX:ParallelGCThreads=4  |
| -XX:MaxGCPauseMillis=n  | 设置Parallel Scavenge回收的最大时间(毫秒)                    | -XX:MaxGCPauseMillis=100 |
| -XX:GCTimeRatio=n       | 设置Parallel Scavenge垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) | -XX:GCTimeRatio=19       |
| -XX:+UseParallelOldGC   | 设置老年代为并行收集器ParallelOld收集器                      |                          |
| -XX:+UseConcMarkSweepGC | 设置老年代并发收集器CMS                                      |                          |
| -XX:+CMSIncrementalMode | 设置CMS收集器为增量模式，适用于单CPU情况。                   |                          |&lt;/p&gt;
</description>
        <pubDate>Wed, 20 Feb 2019 00:00:00 +0000</pubDate>
        <link>http://offery-gong.github.io/2019/02/20/JVM%E8%B0%83%E4%BC%98/</link>
        <guid isPermaLink="true">http://offery-gong.github.io/2019/02/20/JVM%E8%B0%83%E4%BC%98/</guid>
        
        <category>JVM</category>
        
        
      </item>
    
      <item>
        <title>数据库原理</title>
        <description>&lt;h1 id=&quot;一事务&quot;&gt;一、事务&lt;/h1&gt;

&lt;h2 id=&quot;概念&quot;&gt;概念&lt;/h2&gt;

&lt;p&gt;事务指的是满足 ACID 特性的一组操作，可以通过 Commit 提交一个事务，也可以使用 Rollback 进行回滚。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/731a5e8f-a2c2-43ff-b8dd-6aeb9fffbe26.jpg&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;acid&quot;&gt;ACID&lt;/h2&gt;

&lt;h3 id=&quot;1-原子性atomicity&quot;&gt;1. 原子性（Atomicity）&lt;/h3&gt;

&lt;p&gt;事务被视为不可分割的最小单元，事务的所有操作要么全部提交成功，要么全部失败回滚。&lt;/p&gt;

&lt;p&gt;回滚可以用回滚日志来实现，回滚日志记录着事务所执行的修改操作，在回滚时反向执行这些修改操作即可。&lt;/p&gt;

&lt;p&gt;==也即：数据库事务的原子性通过日志实现。==&lt;/p&gt;

&lt;h3 id=&quot;2-一致性consistency&quot;&gt;2. 一致性（Consistency）&lt;/h3&gt;

&lt;p&gt;数据库在事务执行前后都保持一致性状态。在一致性状态下，所有事务对一个数据的读取结果都是相同的。&lt;/p&gt;

&lt;h3 id=&quot;3-隔离性isolation&quot;&gt;3. 隔离性（Isolation）&lt;/h3&gt;

&lt;p&gt;一个事务所做的修改在最终提交以前，对其它事务是不可见的。&lt;/p&gt;

&lt;h3 id=&quot;4-持久性durability&quot;&gt;4. 持久性（Durability）&lt;/h3&gt;

&lt;p&gt;一旦事务提交，则其所做的修改将会永远保存到数据库中。即使系统发生崩溃，事务执行的结果也不能丢失。&lt;/p&gt;

&lt;p&gt;使用重做日志来保证持久性。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;事务的 ACID 特性概念简单，但不是很好理解，主要是因为这几个特性不是一种平级关系：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;只有满足一致性，事务的执行结果才是正确的。&lt;/li&gt;
  &lt;li&gt;在无并发的情况下，事务串行执行，隔离性一定能够满足。此时只要能满足原子性，就一定能满足一致性。&lt;/li&gt;
  &lt;li&gt;==在并发的情况下，多个事务并行执行，事务不仅要满足原子性，还需要满足隔离性，才能满足一致性。==&lt;/li&gt;
  &lt;li&gt;事务满足持久化是为了能应对数据库崩溃的情况。&lt;/li&gt;
&lt;/ul&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/8036ba3d-8de9-44aa-bf5d-1f8ca18ae89b.jpg&quot; width=&quot;700&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;autocommit&quot;&gt;AUTOCOMMIT&lt;/h2&gt;

&lt;p&gt;MySQL 默认采用自动提交模式。也就是说，如果不显式使用&lt;code class=&quot;highlighter-rouge&quot;&gt;START TRANSACTION&lt;/code&gt;语句来开始一个事务，那么每个查询都会被当做一个事务自动提交。&lt;/p&gt;

&lt;h1 id=&quot;二并发一致性问题&quot;&gt;二、并发一致性问题&lt;/h1&gt;

&lt;p&gt;在并发环境下，事务的隔离性很难保证，因此会出现很多并发一致性问题。&lt;/p&gt;

&lt;h2 id=&quot;丢失修改&quot;&gt;丢失修改&lt;/h2&gt;

&lt;p&gt;T&lt;sub&gt;1&lt;/sub&gt; 和 T&lt;sub&gt;2&lt;/sub&gt; 两个事务都对一个数据进行修改，T&lt;sub&gt;1&lt;/sub&gt; 先修改，T&lt;sub&gt;2&lt;/sub&gt; 随后修改，T&lt;sub&gt;2&lt;/sub&gt; 的修改覆盖了 T&lt;sub&gt;1&lt;/sub&gt; 的修改。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/26a7c9df-22f6-4df4-845a-745c053ab2e5.jpg&quot; width=&quot;350&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;读脏数据&quot;&gt;读脏数据&lt;/h2&gt;

&lt;p&gt;T&lt;sub&gt;1&lt;/sub&gt; 修改一个数据，T&lt;sub&gt;2&lt;/sub&gt; 随后读取这个数据。如果 T&lt;sub&gt;1&lt;/sub&gt; 撤销了这次修改，那么 T&lt;sub&gt;2&lt;/sub&gt; 读取的数据是脏数据。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/bab0fba6-38e4-45f7-b34d-3edaad43810f.jpg&quot; width=&quot;400&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;不可重复读&quot;&gt;不可重复读&lt;/h2&gt;

&lt;p&gt;T&lt;sub&gt;2&lt;/sub&gt; 读取一个数据，T&lt;sub&gt;1&lt;/sub&gt; 对该数据做了修改。如果 T&lt;sub&gt;2&lt;/sub&gt; 再次读取这个数据，此时读取的结果和第一次读取的结果不同。&lt;/p&gt;

&lt;p&gt;==即：读到了T2对数据的update 和delete 等修改操作==&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/43bf0957-0386-4c09-9ad7-e163c5b62559.jpg&quot; width=&quot;400&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;幻影读&quot;&gt;幻影读&lt;/h2&gt;

&lt;p&gt;T&lt;sub&gt;1&lt;/sub&gt; 读取某个范围的数据，T&lt;sub&gt;2&lt;/sub&gt; 在这个范围内插入新的数据，T&lt;sub&gt;1&lt;/sub&gt; 再次读取这个范围的数据，此时读取的结果和和第一次读取的结果不同。&lt;/p&gt;

&lt;p&gt;幻读关键在于插入操作insert；T2进行了插入，然后提交，之后T1读到了之前不存在的数据行；因为只加行锁，在；&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/2959e455-e6cb-4461-aeb3-e319fe5c41db.jpg&quot; width=&quot;400&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;
—-&lt;/p&gt;

&lt;p&gt;产生并发不一致性问题主要原因是破坏了事务的隔离性，解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现，但是封锁操作需要用户自己控制，相当复杂。数据库管理系统提供了事务的隔离级别，让用户以一种更轻松的方式处理并发一致性问题。&lt;/p&gt;

&lt;h1 id=&quot;三封锁&quot;&gt;三、封锁&lt;/h1&gt;

&lt;h2 id=&quot;封锁粒度&quot;&gt;封锁粒度&lt;/h2&gt;

&lt;p&gt;MySQL 中提供了两种封锁粒度：行级锁以及表级锁。（InnoDB都支持）&lt;/p&gt;

&lt;p&gt;应该尽量只锁定需要修改的那部分数据，而不是所有的资源。锁定的数据量越少，发生锁争用的可能就越小，系统的并发程度就越高。&lt;/p&gt;

&lt;p&gt;但是加锁需要消耗资源，锁的各种操作（包括获取锁、释放锁、以及检查锁状态）都会增加系统开销。因此封锁粒度越小，系统开销就越大。&lt;/p&gt;

&lt;p&gt;在选择封锁粒度时，需要在锁开销和并发程度之间做一个权衡。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/1a851e90-0d5c-4d4f-ac54-34c20ecfb903.jpg&quot; width=&quot;300&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;封锁类型&quot;&gt;封锁类型&lt;/h2&gt;

&lt;h3 id=&quot;1-读写锁&quot;&gt;1. 读写锁&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;排它锁（Exclusive），简写为 X 锁，又称写锁。&lt;/li&gt;
  &lt;li&gt;共享锁（Shared），简写为 S 锁，又称读锁。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有以下两个规定：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一个事务对数据对象 A 加了 X 锁，就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。&lt;/li&gt;
  &lt;li&gt;一个事务对数据对象 A 加了 S 锁，可以对 A 进行读取操作，但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁，但是不能加 X 锁。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;锁的兼容关系如下：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;-&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;X&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;S&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;X&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;S&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;2-意向锁&quot;&gt;2. 意向锁&lt;/h3&gt;

&lt;p&gt;使用意向锁（Intention Locks）可以更容易地支持多粒度封锁。&lt;/p&gt;

&lt;p&gt;在存在行级锁和表级锁的情况下，事务 T 想要对表 A 加 X 锁，就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁，那么就需要对表 A 的每一行都检测一次，这是非常耗时的。&lt;/p&gt;

&lt;p&gt;意向锁在原来的 X/S 锁之上引入了 IX/IS，IX/IS 都是表锁，用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一个事务在获得某个数据行对象的 S 锁之前，必须先获得表的 IS 锁或者更强的锁；&lt;/li&gt;
  &lt;li&gt;一个事务在获得某个数据行对象的 X 锁之前，必须先获得表的 IX 锁。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;通过引入意向锁，事务 T 想要对表 A 加 X 锁，只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁，如果加了就表示有其它事务正在使用这个表或者表中某一行的锁，因此事务 T 加 X 锁失败。&lt;/p&gt;

&lt;p&gt;各种锁的兼容关系如下：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;-&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;X&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;IX&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;S&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;IS&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;X&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;IX&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;S&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;IS&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;解释如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;任意 IS/IX 锁之间都是兼容的，因为它们只是表示想要对表加锁，而不是真正加锁；&lt;/li&gt;
  &lt;li&gt;S 锁只与 S 锁和 IS 锁兼容，也就是说事务 T 想要对数据行加 S 锁，其它事务可以已经获得对表或者表中的行的 S 锁。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;封锁协议&quot;&gt;封锁协议&lt;/h2&gt;

&lt;h3 id=&quot;1-三级封锁协议&quot;&gt;1. 三级封锁协议&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;一级封锁协议&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;事务 T 要修改数据 A 时必须加 X 锁，直到 T 结束才释放锁。&lt;/p&gt;

&lt;p&gt;可以解决丢失修改问题，因为不能同时有两个事务对同一个数据进行修改，那么事务的修改就不会被覆盖。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;T&lt;sub&gt;1&lt;/sub&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;T&lt;sub&gt;2&lt;/sub&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;lock-x(A)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;read A=20&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;lock-x(A)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;wait&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;write A=19&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;commit&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;unlock-x(A)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;obtain&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;read A=19&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;write A=21&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;commit&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;unlock-x(A)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;二级封锁协议&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在一级的基础上，要求读取数据 A 时必须加 S 锁，读取完马上释放 S 锁。&lt;/p&gt;

&lt;p&gt;可以解决读脏数据问题，因为如果一个事务在对数据 A 进行修改，根据 1 级封锁协议，会加 X 锁，那么就不能再加 S 锁了，也就是不会读入数据。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;T&lt;sub&gt;1&lt;/sub&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;T&lt;sub&gt;2&lt;/sub&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;lock-x(A)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;read A=20&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;write A=19&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;lock-s(A)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;wait&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;rollback&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;A=20&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;unlock-x(A)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;obtain&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;read A=20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;unlock-s(A)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;commit&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;三级封锁协议&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在二级的基础上，要求读取数据 A 时必须加 S 锁，直到事务结束了才能释放 S 锁。&lt;/p&gt;

&lt;p&gt;可以解决不可重复读的问题，因为读 A 时，其它事务不能对 A 加 X 锁，从而避免了在读的期间数据发生改变。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;T&lt;sub&gt;1&lt;/sub&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;T&lt;sub&gt;2&lt;/sub&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;lock-s(A)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;read A=20&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;lock-x(A)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;wait&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;read A=20&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;commit&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;unlock-s(A)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;obtain&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;read A=20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;write A=19&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;commit&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;unlock-X(A)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;2-两段锁协议&quot;&gt;2. 两段锁协议&lt;/h3&gt;

&lt;p&gt;加锁和解锁分为两个阶段进行。&lt;/p&gt;

&lt;p&gt;可串行化调度是指，通过并发控制，使得并发执行的事务结果与某个串行执行的事务结果相同。&lt;/p&gt;

&lt;p&gt;事务遵循两段锁协议是保证可串行化调度的充分条件。例如以下操作满足两段锁协议，它是可串行化调度。&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但不是必要条件，例如以下操作不满足两段锁协议，但是它还是可串行化调度。&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;mysql-隐式与显示锁定&quot;&gt;MySQL 隐式与显示锁定&lt;/h2&gt;

&lt;p&gt;MySQL 的 InnoDB 存储引擎采用两段锁协议，会根据隔离级别在需要的时候自动加锁，并且所有的锁都是在同一时刻被释放，这被称为隐式锁定。&lt;/p&gt;

&lt;p&gt;InnoDB 也可以使用特定的语句进行显示锁定：&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;LOCK&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;In&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SHARE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;MODE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;四隔离级别&quot;&gt;四、隔离级别&lt;/h1&gt;

&lt;h2 id=&quot;未提交读read-uncommitted&quot;&gt;未提交读（READ UNCOMMITTED）&lt;/h2&gt;

&lt;p&gt;事务中的修改，即使没有提交，对其它事务也是可见的。&lt;/p&gt;

&lt;h2 id=&quot;提交读read-committed&quot;&gt;提交读（READ COMMITTED）&lt;/h2&gt;

&lt;p&gt;一个事务只能读取已经提交的事务所做的修改。换句话说，一个事务所做的修改在提交之前对其它事务是不可见的。&lt;/p&gt;

&lt;h2 id=&quot;可重复读repeatable-read&quot;&gt;可重复读（REPEATABLE READ）&lt;/h2&gt;

&lt;p&gt;保证在同一个事务中多次读取同样数据的结果是一样的。&lt;/p&gt;

&lt;h2 id=&quot;可串行化serializable&quot;&gt;可串行化（SERIALIZABLE）&lt;/h2&gt;

&lt;p&gt;强制事务串行执行。&lt;/p&gt;

&lt;hr /&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;隔离级别&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;脏读&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;不可重复读&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;幻影读&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;加锁读&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;未提交读&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;提交读&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;可重复读&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;可串行化&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;×&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;√&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h1 id=&quot;五多版本并发控制&quot;&gt;五、多版本并发控制&lt;/h1&gt;

&lt;p&gt;==多版本并发控制==（Multi-Version Concurrency Control, MVCC）是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式，==用于实现提交读和可重复读这两种隔离级别==。而未提交读隔离级别总是读取最新的数据行，无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁，单纯使用 MVCC 无法实现。&lt;/p&gt;

&lt;h2 id=&quot;版本号&quot;&gt;版本号&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;系统版本号：是一个递增的数字，每开始一个新的事务，系统版本号就会自动递增。&lt;/li&gt;
  &lt;li&gt;事务版本号：事务开始时的系统版本号。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;隐藏的列&quot;&gt;隐藏的列&lt;/h2&gt;

&lt;p&gt;MVCC 在每行记录后面都保存着两个隐藏的列，用来存储两个版本号：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;创建版本号：指示创建一个数据行的快照时的系统版本号；&lt;/li&gt;
  &lt;li&gt;删除版本号：如果该快照的删除版本号大于当前事务版本号表示该快照有效，否则表示该快照已经被删除了。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;undo-日志&quot;&gt;Undo 日志&lt;/h2&gt;

&lt;p&gt;MVCC 使用到的快照存储在 Undo 日志中，该日志通过回滚指针把一个数据行（Record）的所有快照连接起来。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/e41405a8-7c05-4f70-8092-e961e28d3112.jpg&quot; width=&quot;&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;实现过程&quot;&gt;实现过程&lt;/h2&gt;

&lt;p&gt;以下实现过程针对&lt;strong&gt;可重复读隔离级别&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;==当开始新一个事务时，该事务的版本号肯定会大于当前所有数据行快照的创建版本号，理解这一点很关键。==&lt;/p&gt;

&lt;h3 id=&quot;1-select&quot;&gt;1. SELECT&lt;/h3&gt;

&lt;p&gt;多个事务必须读取到同一个数据行的快照，并且这个快照是距离现在最近的一个有效快照。但是也有例外，如果有一个事务正在修改该数据行，那么它可以读取事务本身所做的修改，而不用和其它事务的读取结果一致。&lt;/p&gt;

&lt;p&gt;把没有对一个数据行做修改的事务称为 T，T 所要读取的数据行快照的创建版本号必须小于 T 的版本号，因为如果大于或者等于 T 的版本号，那么表示该数据行快照是其它事务的最新修改，因此不能去读取它。除此之外，T 所要读取的数据行快照的删除版本号必须大于 T 的版本号，因为如果小于等于 T 的版本号，那么表示该数据行快照是已经被删除的，不应该去读取它。&lt;/p&gt;

&lt;h3 id=&quot;2-insert&quot;&gt;2. INSERT&lt;/h3&gt;

&lt;p&gt;将当前系统版本号作为数据行快照的创建版本号。&lt;/p&gt;

&lt;h3 id=&quot;3-delete&quot;&gt;3. DELETE&lt;/h3&gt;

&lt;p&gt;将当前系统版本号作为数据行快照的删除版本号。&lt;/p&gt;

&lt;h3 id=&quot;4-update&quot;&gt;4. UPDATE&lt;/h3&gt;

&lt;p&gt;将当前系统版本号作为更新前的数据行快照的删除版本号，并将当前系统版本号作为更新后的数据行快照的创建版本号。可以理解为先执行 DELETE 后执行 INSERT。&lt;/p&gt;

&lt;h2 id=&quot;快照读与当前读&quot;&gt;快照读与当前读&lt;/h2&gt;

&lt;h3 id=&quot;1-快照读&quot;&gt;1. 快照读&lt;/h3&gt;

&lt;p&gt;使用 MVCC 读取的是快照中的数据，这样可以减少加锁所带来的开销。&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2-当前读&quot;&gt;2. 当前读&lt;/h3&gt;

&lt;p&gt;读取的是最新的数据，需要加锁。以下第一个语句需要加 S 锁，其它都需要加 X 锁。&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;lock&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;share&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;六next-key-locks&quot;&gt;六、Next-Key Locks&lt;/h1&gt;

&lt;p&gt;Next-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现。&lt;/p&gt;

&lt;p&gt;MVCC 不能解决幻读的问题，Next-Key Locks 就是为了解决这个问题而存在的。在可重复读（REPEATABLE READ）隔离级别下，使用 MVCC + Next-Key Locks 可以解决幻读问题。&lt;/p&gt;

&lt;h2 id=&quot;record-locks&quot;&gt;Record Locks&lt;/h2&gt;

&lt;p&gt;锁定一个记录上的索引，而不是记录本身。&lt;/p&gt;

&lt;p&gt;如果表没有设置索引，InnoDB 会自动在主键上创建隐藏的聚簇索引，因此 Record Locks 依然可以使用。&lt;/p&gt;

&lt;h2 id=&quot;gap-locks&quot;&gt;Gap Locks&lt;/h2&gt;

&lt;p&gt;锁定索引之间的间隙，但是不包含索引本身。例如当一个事务执行以下语句，其它事务就不能在 t.c 中插入 15。&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BETWEEN&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;next-key-locks&quot;&gt;Next-Key Locks&lt;/h2&gt;

&lt;p&gt;它是 Record Locks 和 Gap Locks 的结合，不仅锁定一个记录上的索引，也锁定索引之间的间隙。例如一个索引包含以下值：10, 11, 13, and 20，那么就需要锁定以下区间：&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;negative&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;infinity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;positive&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;infinity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;七关系数据库设计理论&quot;&gt;七、关系数据库设计理论&lt;/h1&gt;

&lt;h2 id=&quot;函数依赖&quot;&gt;函数依赖&lt;/h2&gt;

&lt;p&gt;记 A-&amp;gt;B 表示 A 函数决定 B，也可以说 B 函数依赖于 A。&lt;/p&gt;

&lt;p&gt;如果 {A1，A2，… ，An} 是关系的一个或多个属性的集合，该集合函数决定了关系的其它所有属性并且是最小的，那么该集合就称为键码。&lt;/p&gt;

&lt;p&gt;对于 A-&amp;gt;B，如果能找到 A 的真子集 A’，使得 A’-&amp;gt; B，那么 A-&amp;gt;B 就是部分函数依赖，否则就是完全函数依赖。&lt;/p&gt;

&lt;p&gt;对于 A-&amp;gt;B，B-&amp;gt;C，则 A-&amp;gt;C 是一个传递函数依赖。&lt;/p&gt;

&lt;h2 id=&quot;异常&quot;&gt;异常&lt;/h2&gt;

&lt;p&gt;以下的学生课程关系的函数依赖为 Sno, Cname -&amp;gt; Sname, Sdept, Mname, Grade，键码为 {Sno, Cname}。也就是说，确定学生和课程之后，就能确定其它信息。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sno&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sname&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sdept&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Mname&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Cname&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Grade&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;课程-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;90&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;课程-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;80&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;课程-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;100&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;课程-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;95&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;不符合范式的关系，会产生很多异常，主要有以下四种异常：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;冗余数据：例如 &lt;code class=&quot;highlighter-rouge&quot;&gt;学生-2&lt;/code&gt; 出现了两次。&lt;/li&gt;
  &lt;li&gt;修改异常：修改了一个记录中的信息，但是另一个记录中相同的信息却没有被修改。&lt;/li&gt;
  &lt;li&gt;删除异常：删除一个信息，那么也会丢失其它信息。例如删除了 &lt;code class=&quot;highlighter-rouge&quot;&gt;课程-1&lt;/code&gt; 需要删除第一行和第三行，那么 &lt;code class=&quot;highlighter-rouge&quot;&gt;学生-1&lt;/code&gt; 的信息就会丢失。&lt;/li&gt;
  &lt;li&gt;插入异常：例如想要插入一个学生的信息，如果这个学生还没选课，那么就无法插入。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;范式&quot;&gt;范式&lt;/h2&gt;

&lt;p&gt;范式理论是为了解决以上提到四种异常。&lt;/p&gt;

&lt;p&gt;高级别范式的依赖于低级别的范式，1NF 是最低级别的范式。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/c2d343f7-604c-4856-9a3c-c71d6f67fecc.png&quot; width=&quot;300&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;1-第一范式-1nf&quot;&gt;1. 第一范式 (1NF)&lt;/h3&gt;

&lt;p&gt;属性不可分。&lt;/p&gt;

&lt;h3 id=&quot;2-第二范式-2nf&quot;&gt;2. 第二范式 (2NF)&lt;/h3&gt;

&lt;p&gt;每个非主属性完全函数依赖于键码。&lt;/p&gt;

&lt;p&gt;可以通过分解来满足。&lt;/p&gt;

&lt;p&gt;&amp;lt;font size=4&amp;gt; &lt;strong&gt;分解前&lt;/strong&gt; &amp;lt;/font&amp;gt;&lt;br /&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sno&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sname&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sdept&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Mname&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Cname&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Grade&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;课程-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;90&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;课程-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;80&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;课程-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;100&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;课程-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;95&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;以上学生课程关系中，{Sno, Cname} 为键码，有如下函数依赖：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Sno -&amp;gt; Sname, Sdept&lt;/li&gt;
  &lt;li&gt;Sdept -&amp;gt; Mname&lt;/li&gt;
  &lt;li&gt;Sno, Cname-&amp;gt; Grade&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grade 完全函数依赖于键码，它没有任何冗余数据，每个学生的每门课都有特定的成绩。&lt;/p&gt;

&lt;p&gt;Sname, Sdept 和 Mname 都部分依赖于键码，当一个学生选修了多门课时，这些数据就会出现多次，造成大量冗余数据。&lt;/p&gt;

&lt;p&gt;&amp;lt;font size=4&amp;gt; &lt;strong&gt;分解后&lt;/strong&gt; &amp;lt;/font&amp;gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;关系-1&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sno&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sname&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sdept&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Mname&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-2&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;有以下函数依赖：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Sno -&amp;gt; Sname, Sdept&lt;/li&gt;
  &lt;li&gt;Sdept -&amp;gt; Mname&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;关系-2&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sno&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Cname&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Grade&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;课程-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;90&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;课程-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;80&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;课程-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;100&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;课程-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;95&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;有以下函数依赖：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Sno, Cname -&amp;gt;  Grade&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;3-第三范式-3nf&quot;&gt;3. 第三范式 (3NF)&lt;/h3&gt;

&lt;p&gt;非主属性不传递函数依赖于键码。&lt;/p&gt;

&lt;p&gt;上面的 关系-1 中存在以下传递函数依赖：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Sno -&amp;gt; Sdept -&amp;gt; Mname&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以进行以下分解：&lt;/p&gt;

&lt;p&gt;关系-11&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sno&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sname&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sdept&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学生-3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-2&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;关系-12&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Sdept&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Mname&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;学院-2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;院长-2&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h1 id=&quot;八er-图&quot;&gt;八、ER 图&lt;/h1&gt;

&lt;p&gt;Entity-Relationship，有三个组成部分：实体、属性、联系。&lt;/p&gt;

&lt;p&gt;用来进行关系型数据库系统的概念设计。&lt;/p&gt;

&lt;h2 id=&quot;实体的三种联系&quot;&gt;实体的三种联系&lt;/h2&gt;

&lt;p&gt;包含一对一，一对多，多对多三种。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如果 A 到 B 是一对多关系，那么画个带箭头的线段指向 B；&lt;/li&gt;
  &lt;li&gt;如果是一对一，画两个带箭头的线段；&lt;/li&gt;
  &lt;li&gt;如果是多对多，画两个不带箭头的线段。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下图的 Course 和 Student 是一对多的关系。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/292b4a35-4507-4256-84ff-c218f108ee31.jpg&quot; width=&quot;&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;表示出现多次的关系&quot;&gt;表示出现多次的关系&lt;/h2&gt;

&lt;p&gt;一个实体在联系出现几次，就要用几条线连接。&lt;/p&gt;

&lt;p&gt;下图表示一个课程的先修关系，先修关系出现两个 Course 实体，第一个是先修课程，后一个是后修课程，因此需要用两条线来表示这种关系。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/8b798007-e0fb-420c-b981-ead215692417.jpg&quot; width=&quot;&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;联系的多向性&quot;&gt;联系的多向性&lt;/h2&gt;

&lt;p&gt;虽然老师可以开设多门课，并且可以教授多名学生，但是对于特定的学生和课程，只有一个老师教授，这就构成了一个三元联系。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/423f2a40-bee1-488e-b460-8e76c48ee560.png&quot; width=&quot;&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;
一般只使用二元联系，可以把多元联系转换为二元联系。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/de9b9ea0-1327-4865-93e5-6f805c48bc9e.png&quot; width=&quot;&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;表示子类&quot;&gt;表示子类&lt;/h2&gt;

&lt;p&gt;用一个三角形和两条线来连接类和子类，与子类有关的属性和联系都连到子类上，而与父类和子类都有关的连到父类上。&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt; &lt;img src=&quot;pics/7ec9d619-fa60-4a2b-95aa-bf1a62aad408.jpg&quot; width=&quot;&quot; /&gt; &lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h1 id=&quot;九-参考资料&quot;&gt;九 参考资料&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;AbrahamSilberschatz, HenryF.Korth, S.Sudarshan, 等. 数据库系统概念 [M]. 机械工业出版社, 2006.&lt;/li&gt;
  &lt;li&gt;施瓦茨. 高性能 MYSQL(第3版)[M]. 电子工业出版社, 2013.&lt;/li&gt;
  &lt;li&gt;史嘉权. 数据库系统概论[M]. 清华大学出版社有限公司, 2006.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html&quot;&gt;The InnoDB Storage Engine&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.slideshare.net/ErnestoHernandezRodriguez/transaction-isolation-levels&quot;&gt;Transaction isolation levels&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://scanftree.com/dbms/2-phase-locking-protocol&quot;&gt;Concurrency Control&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.slideshare.net/brshristov/the-nightmare-of-locking-blocking-and-isolation-levels-46391666&quot;&gt;The Nightmare of Locking, Blocking and Isolation Levels!&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://aksakalli.github.io/2012/03/12/database-normalization-and-normal-forms-with-an-example.html&quot;&gt;Database Normalization and Normal Forms with an Example&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.jcole.us/2014/04/16/the-basics-of-the-innodb-undo-logging-and-history-system/&quot;&gt;The basics of the InnoDB undo logging and history system&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.brightbox.com/blog/2013/10/31/on-mysql-locks/&quot;&gt;MySQL locking for the busy web developer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://draveness.me/mysql-innodb&quot;&gt;浅入浅出 MySQL 和 InnoDB&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tech.meituan.com/2014/08/20/innodb-lock.html&quot;&gt;Innodb 中的事务隔离级别和锁的关系&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;1mysql事务日志&quot;&gt;1.MySQL事务日志&lt;/h1&gt;

&lt;h2 id=&quot;回滚日志undo-log&quot;&gt;回滚日志（undo log）&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;保证事务原子性&lt;/li&gt;
  &lt;li&gt;参与MVCC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;想要保证事务的原子性，就需要在异常发生时，对已经执行的操作进行回滚，而在 MySQL 中，恢复机制是通过回滚日志（undo log）实现的，所有事务进行的修改都会先记录到这个回滚日志中，然后在对数据库中的对应行进行写入。&lt;/p&gt;

&lt;p&gt;回滚日志并不能将数据库物理地恢复到执行语句或者事务之前的样子；它是逻辑日志，当回滚日志被使用时，它只会按照日志&lt;strong&gt;逻辑地&lt;/strong&gt;将数据库中的修改撤销掉看，可以&lt;strong&gt;理解&lt;/strong&gt;为，我们在事务中使用的每一条 &lt;code class=&quot;highlighter-rouge&quot;&gt;INSERT&lt;/code&gt; 都对应了一条 &lt;code class=&quot;highlighter-rouge&quot;&gt;DELETE&lt;/code&gt;，每一条 &lt;code class=&quot;highlighter-rouge&quot;&gt;UPDATE&lt;/code&gt; 也都对应一条相反的 &lt;code class=&quot;highlighter-rouge&quot;&gt;UPDATE&lt;/code&gt; 语句。因为存在着其他的并发事务对数据进行修改，所以不能简单的将数据物理恢复到初始状态；&lt;/p&gt;

&lt;h2 id=&quot;重做日志redo-log-&quot;&gt;重做日志（Redo Log ）&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;实现事务持久性&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;redo log包含：一是内存中的&lt;strong&gt;重做日志缓冲区&lt;/strong&gt;，因为重做日志缓冲区在内存中，所以它是易失的；另一个就是在磁盘上的&lt;strong&gt;重做日志文件&lt;/strong&gt;，它是持久的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://wx4.sinaimg.cn/large/d8b81fbfly1g1ahe3sq5oj21uo0nwgmo.jpg&quot; alt=&quot;Redo-Logging&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当我们在一个事务中尝试对数据进行修改时，它会先将数据从磁盘读入内存，并更新内存中缓存的数据，然后生成一条重做日志并写入重做日志缓存，当事务真正提交时，MySQL 会将重做日志缓存中的内容刷新到重做日志文件，再将内存中的数据更新到磁盘上，图中的第 4、5 步就是在事务提交时执行的。&lt;/p&gt;

&lt;p&gt;在 InnoDB 中，重做日志都是以 512 字节的块的形式进行存储的，同时因为块的大小与磁盘扇区大小相同，所以重做日志的写入可以保证原子性，不会由于机器断电导致重做日志仅写入一半并留下脏数据。&lt;/p&gt;

&lt;p&gt;除了所有对数据库的修改会产生重做日志，因为&lt;strong&gt;回滚日志也是需要持久存储的，它们也会创建对应的重做日志&lt;/strong&gt;，在发生错误后，数据库重启时会从重做日志中找出未被更新到数据库磁盘中的日志重新执行以满足事务的持久性。&lt;/p&gt;

&lt;h2 id=&quot;回滚日志和重做日志&quot;&gt;回滚日志和重做日志&lt;/h2&gt;

&lt;p&gt;MySQL中存在两种事务日志：回滚日志（undo日志）和重做日志（redo日志）在数据库系统中，事务的原子性和持久性是由事务日志（transaction log）保证的，在实现时也就是上面提到的两种日志，前者用于对事务的影响进行撤销，后者在错误处理时对已经提交的事务进行重做，它们能保证两点：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;发生错误或者需要回滚的事务能够成功回滚（&lt;strong&gt;原子性&lt;/strong&gt;）；&lt;/li&gt;
  &lt;li&gt;在事务提交后，数据没来得及写会磁盘就宕机时，在下次重新启动后能够成功恢复数据（&lt;strong&gt;持久性&lt;/strong&gt;）；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在数据库中，这两种日志经常都是一起工作的，我们可以将它们整体看做一条事务日志，其中包含了&lt;strong&gt;事务的 ID、修改的行元素以及修改前后的值&lt;/strong&gt;。一条事务日志同时包含了修改前后的值，能够非常简单的进行回滚和重做两种操作&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;C:\Users\PC\AppData\Roaming\Typora\typora-user-images\1551959187349.png&quot; alt=&quot;1551959187349&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;2多版本mvcc实现mysql隔离性&quot;&gt;2.多版本MVCC实现MySQL隔离性&lt;/h1&gt;

&lt;h2 id=&quot;行结构&quot;&gt;行结构&lt;/h2&gt;

&lt;p&gt;每一行额外包含三个隐藏字段：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ws3.sinaimg.cn/large/d8b81fbfly1g1ahehkl6oj20gl01y0sl.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;DB_TRX_ID：事务ID。行的创建时间和删除时间记录的就是此值。&lt;/li&gt;
  &lt;li&gt;DB_ROLL_PTR：指向当前记录项的undo信息。&lt;/li&gt;
  &lt;li&gt;DB_ROW_ID:：随着新行插入单调递增的一个字段。当由innodb自动产生聚集索引时，聚集索引包括这个DB_ROW_ID的值，不然的话聚集索引中不包括这个值。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;==当开始新一个事务时，该事务的版本号肯定会大于当前所有数据行快照的创建版本号，理解这一点很关键。==&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在insert操作时，创建时间 = DB_ROW_ID，这时，“删除时间 ”是未定义的。&lt;/li&gt;
  &lt;li&gt;在update操作时，复制新增行的“创建时间”=DB_ROW_ID，删除时间未定义，旧数据行“创建时间”不变，删除时间=该事务的DB_ROW_ID。&lt;/li&gt;
  &lt;li&gt;在delete操作时，相应数据行的“创建时间”不变，删除时间 = 该事务的DB_ROW_ID。&lt;/li&gt;
  &lt;li&gt;select操作对两者都不修改，只读相应的数据。读取创建版本号&amp;lt;当前事务版本号，删除版本号为空或&amp;gt;当前事务版本号。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;事务链表&quot;&gt;事务链表&lt;/h2&gt;

&lt;p&gt;MySQL中的事务在开始到提交这段过程中，都会被保存到一个叫trx_sys的事务链表中（事务系统维护一个全局的list，用于记录还未committed的事务），这是一个基本的链表结构：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ws3.sinaimg.cn/large/d8b81fbfly1g1ahfduj2jj209z02y3yd.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;事务链表中保存的都是还未提交的事务，事务一旦被提交，则会被从事务链表中摘除。&lt;/p&gt;

&lt;h2 id=&quot;readview快照&quot;&gt;ReadView（快照）&lt;/h2&gt;

&lt;p&gt;ReadView说白了就是一个数据结构，在Select语句开始的时候被创建。这个数据结构中包含了3个主要的成员：ReadView{low_trx_id, up_trx_id, trx_ids}，在并发情况下，一个事务在启动时，trx_sys链表中存在部分还未提交的事务，那么哪些改变对当前事务是可见的，哪些又是不可见的，这个需要通过ReadView来进行判定，首先来看下ReadView中的3个成员各自代表的意思：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;low_trx_id&lt;/code&gt;表示该Select启动时，当前事务链表中最大的事务id编号，也就是最近创建的除自身以外最大事务编号；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;up_trx_id&lt;/code&gt;表示该Select启动时，当前事务链表中最小的事务id编号，也就是当前系统中创建最早但还未提交的事务；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;trx_ids&lt;/code&gt;表示所有事务链表中事务的id集合。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;http://wx1.sinaimg.cn/large/d8b81fbfly1g1agrop4erj20b7050wef.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;关于low_limit_id，up_limit_id的理解：（就是low_trx_id，up_trx_id）&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;up_limit_id：当前已经提交的事务号 + 1，事务号 &amp;lt; up_limit_id ，对于当前Read View都是可见的。理解起来就是创建Read View视图的时候，之前已经提交的事务对于该事务肯定是可见的。&lt;/li&gt;
  &lt;li&gt;low_limit_id：当前最大的事务号 + 1，事务号 &amp;gt;= low_limit_id，对于当前Read View都是不可见的。理解起来就是在创建Read View视图之后创建的事务对于该事务肯定是不可见的。（==注意这个low_limit_id=未开启的事务id=当前最大事务id+1==）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外，trx_ids为活跃事务id列表，即Read View初始化时当前未提交的事务列表。所以当进行RR读的时候，trx_ids中的事务对于本事务是不可见的（除了自身事务，自身事务对于表的修改对于自己当然是可见的）。理解起来就是创建RV时，将当前活跃事务ID记录下来，后续即使他们提交对于本事务也是不可见的。&lt;/p&gt;

&lt;h3 id=&quot;readview创建时机&quot;&gt;ReadView创建时机&lt;/h3&gt;

&lt;p&gt;另外, 对于read view快照的生成时机, 也非常关键, &lt;strong&gt;正是因为生成时机的不同, 造成了RC,RR两种隔离级别的不同可见性&lt;/strong&gt;;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在innodb中(默认repeatable read级别), 事务在begin/start transaction之后的第一条select读操作后, 会创建一个快照(read view), 将当前系统中活跃的其他事务记录记录起来;&lt;/li&gt;
  &lt;li&gt;在innodb中(默认repeatable committed级别), 事务中每条select语句都会创建一个快照(read view);&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;undo-log&quot;&gt;Undo Log&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Undo log是InnoDB MVCC事务特性的重要组成部分。&lt;strong&gt;当我们对记录做了变更操作时就会产生undo记录&lt;/strong&gt;，Undo记录默认被记录到系统表空间(ibdata)中，但从5.6开始，也可以使用独立的Undo 表空间。&lt;/li&gt;
  &lt;li&gt;Undo记录中存储的是老版本数据，当一个旧的事务需要读取数据时，为了能读取到老版本的数据，需要顺着undo链找到满足其可见性的记录。当版本链很长时，通常可以认为这是个比较耗时的操作&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;可见性比较算法&quot;&gt;可见性比较算法&lt;/h2&gt;

&lt;p&gt;设要读取的行的最后提交事务id(即当前数据行的稳定事务id)为 &lt;code class=&quot;highlighter-rouge&quot;&gt;trx_id_current&lt;/code&gt;
当前新开事务id为 &lt;code class=&quot;highlighter-rouge&quot;&gt;new_id&lt;/code&gt;
当前新开事务创建的快照&lt;code class=&quot;highlighter-rouge&quot;&gt;read view&lt;/code&gt; 中最早的事务id为&lt;code class=&quot;highlighter-rouge&quot;&gt;up_limit_id&lt;/code&gt;, 最迟的事务id为&lt;code class=&quot;highlighter-rouge&quot;&gt;low_limit_id&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;1.&lt;code class=&quot;highlighter-rouge&quot;&gt;trx_id_current &amp;lt; up_limit_id&lt;/code&gt;, 这种情况比较好理解, 表示, 新事务在读取该行记录时, 该行记录的稳定事务ID是小于, 系统当前所有活跃的事务, 所以当前行稳定数据对新事务可见, 跳到步骤5.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;2.&lt;code class=&quot;highlighter-rouge&quot;&gt;trx_id_current &amp;gt;= low_limit_id&lt;/code&gt;, 这种情况也比较好理解, 表示, 数据行在当前事务启动后被修改，对当前事务不可见。该行记录的稳定事务id是在本次新事务创建之后才开启的，所以该行记录的当前值不可见, 跳到步骤4。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;3.&lt;code class=&quot;highlighter-rouge&quot;&gt;up_limit_id&amp;lt;= trx_id_current &amp;lt;= low_limit_id&lt;/code&gt;, 表示: 该行记录所在事务在本次新事务创建的时候处于活动状态，从&lt;code class=&quot;highlighter-rouge&quot;&gt;up_limit_id&lt;/code&gt;到&lt;code class=&quot;highlighter-rouge&quot;&gt;low_limit_id&lt;/code&gt;进行遍历，如果&lt;code class=&quot;highlighter-rouge&quot;&gt;trx_id_current&lt;/code&gt;等于他们之中的某个事务id的话，那么不可见, 跳到步骤4，&lt;strong&gt;否则表示可见&lt;/strong&gt;（比如&lt;code class=&quot;highlighter-rouge&quot;&gt;trx_sys&lt;/code&gt;为[4,8]；区间内有一个&lt;code class=&quot;highlighter-rouge&quot;&gt;trx_id_current=5.5&lt;/code&gt;的事务，但是它在创建ReadView时候就已经提交了。在RR隔离级别下，对于这种情况是不可见的；而对于RC级别，则是可见的；而对于处于[4,8]区间的&lt;code class=&quot;highlighter-rouge&quot;&gt;trx_sys&lt;/code&gt;中的trxi（表示&lt;code class=&quot;highlighter-rouge&quot;&gt;trx_sys&lt;/code&gt;list中保存的事务），则对RC，RR都不可见，因为这些都是没有提交的事务）。==这里关键点在于，判断的时候是按照&lt;code class=&quot;highlighter-rouge&quot;&gt;trx_id_current&lt;/code&gt;是否在[up_limit_id, low_limit_id]区间判断的；而ReadView则是保存的一个个事务号，是离散的，所以要判断在这个中间是否有已经提交的事务。==&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;4.从该行记录的 &lt;code class=&quot;highlighter-rouge&quot;&gt;DB_ROLL_PTR&lt;/code&gt; 指针所指向的回滚段中取出最新的&lt;code class=&quot;highlighter-rouge&quot;&gt;undo-log的版本号&lt;/code&gt;, 将它赋值该 &lt;code class=&quot;highlighter-rouge&quot;&gt;trx_id_current&lt;/code&gt;，然后跳到步骤1重新开始判断。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;5.将该可见行的值返回。&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;https://pic4.zhimg.com/v2-77c276015661224f1ddaa0ce9be03d0f_r.jpg&quot; alt=&quot;preview&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;快照读与当前读-1&quot;&gt;快照读与当前读&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;MySQL的InnoDB存储引擎默认事务隔离级别是RR(可重复读), 是通过 ==Next-Key-Lock(“行排他锁+MVCC”)== 一起实现的, 不仅可以保证可重复读, 还可以&lt;strong&gt;部分&lt;/strong&gt;防止幻读, 而非完全防止;&lt;/li&gt;
  &lt;li&gt;为什么是部分防止幻读, 而不是完全防止?
    &lt;ul&gt;
      &lt;li&gt;效果: 在如果事务B在事务A执行中, insert了一条数据并提交, 事务A再次查询, 虽然读取的是undo中的旧版本数据(防止了部分幻读), 但是事务A中执行update或者delete都是可以成功的!!&lt;/li&gt;
      &lt;li&gt;因为在innodb中的操作可以分为&lt;code class=&quot;highlighter-rouge&quot;&gt;当前读(current read)&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;快照读(snapshot read)&lt;/code&gt;:&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;1-快照读-1&quot;&gt;1. 快照读&lt;/h3&gt;

&lt;p&gt;使用 MVCC 读取的是快照中的数据，这样可以减少加锁所带来的开销。使用简单的select语句；&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;（不包括&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;这种）&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2-当前读-1&quot;&gt;2. 当前读&lt;/h3&gt;

&lt;p&gt;读取的是最新的数据，需要加锁。以下第一个语句需要加 S 锁，其它都需要加 X 锁。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InnoDB引擎在加锁的时候，只有通过索引进行检索的时候才会使用行级锁，否则会使用表级锁。&lt;/strong&gt;这里我们希望使用行级锁，就要给method_name添加索引，值得注意的是，这个索引一定要创建成唯一索引，否则会出现多个重载方法之间无法同时被访问的问题。重载方法的话建议把参数类型也加上。&lt;/p&gt;

&lt;p&gt;我们可以认为获得排它锁的线程即可获得分布式锁&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;lock&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;share&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在RR级别下，快照读是通过MVVC(多版本控制)和undo log来实现的，当前读是通过加record lock(记录锁)和gap lock(间隙锁)来实现的。&lt;/p&gt;

&lt;p&gt;总结：RR级别下，在读的时候（简单的select语句）采用通过MVCC+undo实现的快照读；在写/更改操作时候，采用当前读，通过Next-Key-Lock实现，实现了真正意义上避免幻读；&lt;/p&gt;

&lt;p&gt;==innodb在快照读的情况下并没有真正的避免幻读, 但是在当前读的情况下避免了不可重复读和幻读!!!==&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;参考文献&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;https://segmentfault.com/a/1190000012650596&lt;/p&gt;

&lt;p&gt;http://mysql.taobao.org/monthly/2017/12/01/&lt;/p&gt;

&lt;p&gt;https://tech.meituan.com/2014/08/20/innodb-lock.html&lt;/p&gt;

&lt;h1 id=&quot;3分布式锁实现&quot;&gt;3.&lt;a href=&quot;http://www.hollischuang.com/archives/1716&quot;&gt;分布式锁实现&lt;/a&gt;&lt;/h1&gt;

&lt;h2 id=&quot;31-分布式锁的疑问&quot;&gt;3.1 分布式锁的疑问&lt;/h2&gt;

&lt;p&gt;谈到分布式锁，有很多实现方式，如数据库、redis、ZooKeeper等。提个问题：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;实现分布式锁需要满足哪些条件呢？&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;32-数据库实现分布式锁&quot;&gt;3.2 数据库实现分布式锁&lt;/h2&gt;

&lt;h2 id=&quot;321-实现案例&quot;&gt;3.2.1 实现案例&lt;/h2&gt;

&lt;p&gt;如使用数据库事务中的锁如record lock来实现，如下所示&lt;/p&gt;

&lt;p&gt;1 获取锁&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAutoCommit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xxx&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;结果不为空&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;//代表获取到锁&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;

        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//为空或者抛异常的话都表示没有获取到锁&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LockException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2 释放锁&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;数据库的lock表，lock_name是主键,通过for update操作，数据库就会对该行记录加上record lock，从而阻塞其他人对该记录的操作。&lt;strong&gt;注意：InnoDB引擎在加锁的时候，只有通过索引进行检索的时候才会使用行级锁，否则会使用表级锁。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;一旦获取到了锁，就可以开始执行业务逻辑，最后通过connection.commit()操作来释放锁。&lt;/p&gt;

&lt;p&gt;其他没有获取到锁的就会阻塞在上述select语句上，可能的结果有2种，在超时之前获取到了锁，在超时之前仍未获取到锁（这时候会抛出超时异常，然后进行重试）&lt;/p&gt;

&lt;p&gt;数据库当然还有其他方式，如插入一个有唯一约束的数据。成功插入则表示获取到了锁，释放锁就是删除该记录。该方案也有很多问题要解决&lt;/p&gt;

&lt;h2 id=&quot;322-存在的问题&quot;&gt;3.2.2 存在的问题&lt;/h2&gt;

&lt;p&gt;首先性能不是特别高。&lt;/p&gt;

&lt;p&gt;通过数据库的锁来实现多进程之间的互斥，但是这貌似也有一个问题：就是sql超时异常的问题&lt;/p&gt;

&lt;p&gt;jdbc超时具体有3种超时，具体见&lt;a href=&quot;http://mp.weixin.qq.com/s?__biz=MjM5NzMyMjAwMA==&amp;amp;mid=2651477438&amp;amp;idx=1&amp;amp;sn=53c26a85d0af8c18a6f9d15d242c01be&amp;amp;scene=0#wechat_redirect&quot;&gt;深入理解JDBC的超时设置&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;框架层的事务超时&lt;/li&gt;
  &lt;li&gt;jdbc的查询超时&lt;/li&gt;
  &lt;li&gt;Socket的读超时&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这里只涉及到后2种的超时，jdbc的查询超时还好（mysql的jdbc驱动会向服务器发送kill query命令来取消查询），如果一旦出现Socket的读超时，对于如果是同步通信的Socket连接来说(底层实现Connection的可能是同步通信也可能是异步通信)，该连接基本上不能使用了，需要关闭该连接，从新换用新的连接，因为会出现请求和响应错乱的情况，比如jedis出现的类型转换异常，详见&lt;a href=&quot;https://my.oschina.net/pingpangkuangmo/blog/737122&quot;&gt;Jedis的类型转换异常深究&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;33-redis实现分布式锁&quot;&gt;3.3 redis实现分布式锁&lt;/h2&gt;

&lt;p&gt;而redis通常可以使用setnx来实现分布式锁&lt;/p&gt;

&lt;h3 id=&quot;331-基本版&quot;&gt;3.3.1 基本版&lt;/h3&gt;

&lt;p&gt;1 获取锁&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setnx&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lock_ley&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock_timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;//获取到了锁&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//没有获取到锁&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2 释放锁&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;del&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock_ley&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;setnx来创建一个key，如果key不存在则创建成功返回1，如果key已经存在则返回0。依照上述来判定是否获取到了锁&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;获取到锁的执行业务逻辑，完毕后删除lock_key，来实现释放锁&lt;/p&gt;

&lt;p&gt;其他未获取到锁的则进行不断重试，直到自己获取到了锁&lt;/p&gt;

&lt;h3 id=&quot;332-改进版&quot;&gt;3.3.2 改进版&lt;/h3&gt;

&lt;p&gt;上述逻辑在正常情况下是OK的，但是一旦获取到锁的客户端挂了，没有执行上述释放锁的操作，则其他客户端就无法获取到锁了，所以在这种情况下有2种方式来解决：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;为lock_key设置一个过期时间&lt;/li&gt;
  &lt;li&gt;对lock_key的value进行判断是否过期&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以第一种为例，在set键值的时候带上过期时间，即使挂了，也会在过期时间之后，其他客户端能够重新竞争获取锁&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//等同于：ret = setnx lock_key identify_value ex lock_timeout&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock_key&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;identify_value&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock_timeout&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;//获取到了锁&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock_key&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;identify_value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;del&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock_key&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;以第二种为例，一旦发现lock_key的值已经小于当前时间了，说明该key过期了，然后对该key进行getset设置，一旦getset返回值是原来的过期值，说明当前客户端是第一个来操作的，代表获取到了锁，一旦getset返回值不是原来过期时间则说明前面已经有人修改了，则代表没有获取到锁，详细见&lt;a href=&quot;http://www.jeffkit.info/2011/07/1000/&quot;&gt;用Redis实现分布式锁&lt;/a&gt;，改正如下：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_unix_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock_timeout&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SETNX&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GETSET&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 
&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;your&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;do_job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
 
&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;DEL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;foo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里看来第二种其实没有第一种比较好。&lt;/p&gt;

&lt;h3 id=&quot;333-问题依旧&quot;&gt;3.3.3 问题依旧&lt;/h3&gt;

&lt;p&gt;问题1： lock timeout的存在也使得失去了锁的意义，即存在并发的现象。一旦出现锁的租约时间，就意味着&lt;strong&gt;获取到锁的客户端必须在租约之内执行完毕业务逻辑，一旦业务逻辑执行时间过长，租约到期，就会引发并发问题&lt;/strong&gt;。所以有lock timeout的可靠性并不是那么的高。&lt;/p&gt;

&lt;p&gt;问题2： 上述方式仅仅是redis单机情况下，还存在redis单点故障的问题。如果为了解决单点故障而使用redis的sentinel或者cluster方案，则更加复杂，引入的问题更多。&lt;/p&gt;

&lt;h2 id=&quot;34-zookeeper实现分布式锁&quot;&gt;3.4 ZooKeeper实现分布式锁&lt;/h2&gt;

&lt;h3 id=&quot;341-案例&quot;&gt;3.4.1 案例&lt;/h3&gt;

&lt;p&gt;这也是ZooKeeper客户端curator的分布式锁实现。&lt;/p&gt;

&lt;p&gt;1 获取锁&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;在父节点下创建临时顺序节点&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;children&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;获取父节点的所有节点&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;是&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;中的最小的&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;代表获取了节点&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;添加监控前一个节点是否存在的&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;watcher&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;watcher&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;中的内容&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;notifyAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2 释放锁&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;删除上述创建的节点&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;342-总结&quot;&gt;3.4.2 总结&lt;/h3&gt;

&lt;p&gt;ZooKeeper版本的分布式锁问题相对比较来说少。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;锁的占用时间限制&lt;/strong&gt;：redis就有占用时间限制，而ZooKeeper则没有，最主要的原因是redis目前没有办法知道已经获取锁的客户端的状态，是已经挂了呢还是正在执行耗时较长的业务逻辑。而ZooKeeper通过临时节点就能清晰知道，如果临时节点存在说明还在执行业务逻辑，如果临时节点不存在说明已经执行完毕释放锁或者是挂了。由此看来redis如果能像ZooKeeper一样添加一些与客户端绑定的临时键，也是一大好事。&lt;/li&gt;
  &lt;li&gt;是否单点故障：redis本身有很多中玩法，如客户端一致性hash，服务器端sentinel方案或者cluster方案，&lt;strong&gt;很难做到一种分布式锁方式能应对所有这些方案&lt;/strong&gt;。而ZooKeeper只有一种玩法，多台机器的节点数据是一致的，没有redis的那么多的麻烦因素要考虑。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;总体上来说ZooKeeper实现分布式锁更加的简单，可靠性更高。&lt;/p&gt;

&lt;h2 id=&quot;35-分布式锁实现原理总结&quot;&gt;3.5 分布式锁实现原理总结&lt;/h2&gt;

&lt;p&gt;从上面我们经历了3种实现方式，可以从中总结下，该怎么去回答最初提出的问题。&lt;/p&gt;

&lt;h3 id=&quot;351-分布式锁的实现&quot;&gt;3.5.1 分布式锁的实现&lt;/h3&gt;

&lt;p&gt;在我自己看来有如下3个方面：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;怎么获取锁&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;怎么释放锁&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;怎么得知锁被释放了&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;3511-怎么获取锁&quot;&gt;3.5.1.1 怎么获取锁&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;能够提供一种方式，多个客户端并发操作，只能有一个客户端能满足相应的要求&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;如数据库的for update的sql语句、或者插入一个含有唯一约束的数据等&lt;/p&gt;

&lt;p&gt;如redis的setnx等&lt;/p&gt;

&lt;p&gt;如ZooKeeper的求最小节点的方式&lt;/p&gt;

&lt;p&gt;这些都可以保证只能有一个客户端获取到了锁&lt;/p&gt;

&lt;h4 id=&quot;3512-怎么释放锁&quot;&gt;3.5.1.2 怎么释放锁&lt;/h4&gt;

&lt;p&gt;场景一般有2种情况：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;1 正常情况下的释放锁&lt;/li&gt;
  &lt;li&gt;2 异常情况下如何释放锁（即释放锁的操作没有被执行，如挂掉、没执行成功等原因）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如redis正常情况下释放锁是删除lock_key，异常情况下，只能通过lock_key的超时时间了&lt;/p&gt;

&lt;p&gt;如ZooKeeper正常情况下释放锁是删除临时节点，异常情况下，服务器也会主动删除临时节点（这种机制就简单多了）&lt;/p&gt;

&lt;h4 id=&quot;3513-怎么得知锁被释放了&quot;&gt;3.5.1.3 怎么得知锁被释放了&lt;/h4&gt;

&lt;p&gt;实现方式一般有2种情况：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;1 没有获取到锁的客户端不断尝试获取锁&lt;/li&gt;
  &lt;li&gt;2 服务器端通知客户端锁被释放了&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当然第二种情况是最优的（客户端所做的无用功最少），如ZooKeeper通过注册watcher来得到锁释放的通知。而数据库、redis没有办法来通知客户端锁释放了，那客户端就只能傻傻的不断尝试获取锁了。&lt;/p&gt;

&lt;h1 id=&quot;4分布式事务&quot;&gt;4.分布式事务&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;分布式事务（XA）：允许多个独立的事务资源参与到一个全局的事务中，事务资源通常是关系型数据库系统，但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要不全部提交，要不都回滚。使用分布式事务时候，&lt;strong&gt;InnoDB存储引擎的事务隔离级别必须设置serilalizeble&lt;/strong&gt;。&lt;/p&gt;

    &lt;p&gt;分布式事务由一个或者多个&lt;strong&gt;资源管理器&lt;/strong&gt;，一个&lt;strong&gt;事务管理器&lt;/strong&gt;以及一个&lt;strong&gt;应用程序&lt;/strong&gt;。&lt;/p&gt;

    &lt;p&gt;资源管理器：提供访问事务资源的方法。通常一个数据库就是一个资源管理器。&lt;/p&gt;

    &lt;p&gt;事务管理器：连接MySQL服务器的客户端。&lt;/p&gt;

    &lt;p&gt;应用程序：定义事务的边界，指定全局事务中的操作。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;两段式提交：&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;所有参与全局事务的节点都开始准备，告诉&lt;strong&gt;事务管理器&lt;/strong&gt;他们准备好了。&lt;/li&gt;
  &lt;li&gt;事务管理器告诉&lt;strong&gt;资源管理器&lt;/strong&gt;执行rollback还是commit。任何一个节点显示不能提交，就所有节点都被告知需要回滚。&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;通常使用JTA进行事务操作。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;相关问题：&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;分布式系统 微服务架构下，如果有一个订单系统，一个库存系统，怎么保证事务？答： 1 如果是Mysql类型的，利用XA接口，java使用JTA事务。2 可以使用第三方协调者，采用二阶段提交的方法去解决分布式事务， 协调者先发信息给两个数据库，叫他们锁定资源，进行本地事务操作，发送结果回协调者，如果都OK，则commit，如果不行，就回滚。 追问，二阶段提交有什么缺点？ 答：&lt;strong&gt;效率不够高，因为在资源锁定的时候，订单系统不能接受其他请求，业界采用三阶段提交。&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;5sql语句&quot;&gt;5.SQL语句&lt;/h1&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;img src=&quot;http://dl.iteye.com/upload/attachment/282687/6d157c5c-94e4-3959-b8f3-3d2361ab7e78.png&quot; alt=&quot;SQLé¢è¯é¢&quot; /&gt;
    &lt;ol&gt;
      &lt;li&gt;每个科目的最高分：&lt;strong&gt;SELECT kemu,max(score) FROM table GROUP BY kemu;&lt;/strong&gt;&lt;/li&gt;
      &lt;li&gt;java成绩最高的姓名：&lt;strong&gt;SELECT name FROM table WHERE kemu=’java’ and score=(SELECT max(score) FROM table WHERE kemu=’java’);&lt;/strong&gt;&lt;/li&gt;
      &lt;li&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Sat, 02 Feb 2019 00:00:00 +0000</pubDate>
        <link>http://offery-gong.github.io/2019/02/02/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F%E5%8E%9F%E7%90%86/</link>
        <guid isPermaLink="true">http://offery-gong.github.io/2019/02/02/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F%E5%8E%9F%E7%90%86/</guid>
        
        <category>数据库</category>
        
        <category>MySQL</category>
        
        
      </item>
    
  </channel>
</rss>
